皮卡丘打完之后,想找点更接近真实环境的靶场练手,就上了 PortSwigger。结果第一个就踩了坑:一个很简单的搜索型 SQL 注入,试了几种写法结果前后不一致——有的能成功,换一种就报 Internal Server Error

这样能成功:

image-20260903175343395

这样也能成功:

image-20260903175518599

但换成这个就失败:

1
https://0a9d007104202ecc80cc0da60077001d.web-security-academy.net/filter?category=Pets' or 1=1 #

image-20260903175620315

查资料加几轮尝试,才发现是三个坑叠在了一起。

坑一:数据库语法有细微差别

我之前一直学的 MySQL,注释 --# 都能用;但 PortSwigger 这个靶场用的是 PostgreSQL,注释只能用 -- (前后都得有空格)。实战拿不准数据库,就先上 sqlmap 测一下。

顺手把四个库的语法差异整理成了速查:

注释符

  • MySQL:-- (注意后面有空格)、#
  • PostgreSQL:--
  • SQL Server:--
  • Oracle:--

# 只有 MySQL 能用,这就是我刚才踩的坑。通用做法是永远用 -- ,记得后面跟空格。

字符串拼接

  • MySQL:CONCAT('a','b') 或直接 'a' 'b'(空格拼接)
  • PostgreSQL:'a' || 'b'CONCAT('a','b')
  • SQL Server:'a' + 'b'
  • Oracle:'a' || 'b'

UNION 注入里构造字符串的时候,拼接方式不同,用错直接语法报错。

获取版本

  • MySQL:SELECT @@version
  • PostgreSQL:SELECT version()
  • SQL Server:SELECT @@VERSION
  • Oracle:SELECT banner FROM v$version WHERE ROWNUM=1

延时函数(盲注用)

  • MySQL:SLEEP(5)
  • PostgreSQL:PG_SLEEP(5)
  • SQL Server:WAITFOR DELAY '0:0:5'
  • Oracle:DBMS_PIPE.RECEIVE_MESSAGE('a',5)

获取当前数据库名

  • MySQL:SELECT database()
  • PostgreSQL:SELECT current_database()
  • SQL Server:SELECT DB_NAME()
  • Oracle:SELECT ora_database_name FROM dual(Oracle 每个用户就是一个 schema,概念不同)

UNION 注入列数探测

通用技巧都是 ORDER BY N 逐步增大 N 看报错,四个库都一样。但:

  • MySQL:UNION SELECT 1,2,3 可以直接用数字
  • PostgreSQL:UNION SELECT NULL,NULL,NULL 一般用 NULL(部分版本数字也行)
  • SQL Server:UNION SELECT NULL,NULL,NULLUNION ALL SELECT 1,2,3
  • Oracle:UNION SELECT NULL FROM dual 必须有 FROM dual,且每个位置类型要对应

布尔条件

四个库通用:' OR 1=1-- 都能用,这块没区别。

坑二:URL 末尾的空格会被吃掉

还有个坑:URL 最后面如果有空格,是会被直接去掉的,不会自动变成 %20

比如我构造的 ...category=Pets' or 1=1 -- (最后有个空格),直接输进浏览器,前面的 '、空格这些字符浏览器会自动做 URL 编码,但最末尾那个空格就凭空没了,得自己补上。

坑三:URL 里的 # 是片段标识符

抛开上面的语法差异,这里还有个独立的问题:就算数据库是 MySQL,用 # 也不行。因为在 URL 里 # 是片段标识符,浏览器遇到 # 之后的内容根本不会发给服务器。

我之前试过这个:

1
category=Pets%27%20or%201=1%20#

服务器实际收到的只有:

1
category=Pets' or 1=1 

# 后面的东西全被浏览器吃掉了。再叠加后文没注释掉的问题,服务器拼出来的语句是:

1
WHERE category = 'Pets' or 1=1 '

多了一个 ',语法错,于是报 Internal Server Error