前言

何谓短网址(Short URL)?顾名思义,就是形式上比较短的网址,当前主要是借助短网址来替代原先冗长的网址,方便传输和分享。短网址服务也就是将长网址转换为短网址的服务,这种服务在方便了广大网民的同时也带来了一定的安全风险。
Tencent Blade Team专门对短网址的安全问题进行过研究,也在KCON 2018上进行过分享过部分成果,本文也是对议题《短网址的攻击与防御》的解读和补充。
一、短网址基础
短网址服务可以提供一个非常短小的URL以代替原来的可能较长的URL,将长的URL地址缩短。用户访问缩短后的URL时,通常将会重定向到原来的URL。短网址服务主要起源于一些具有字数限制的微博客服务,但是现在广泛用于短信、邮件等。
很多安全问题是跟安全场景相关的,随着场景的不断变化,安全问题也是变化的。短网址的初衷是在微博这种限制字数的公共平台使用,也就是说它基本是公开的,但是后续在个人短信和邮件之中,其实有部分已经是私密的。 这直接引发了短网址第一个比较大的潜在风险。
在了解短网址风险和漏洞之前,我们首先应该了解下短网址是什么以及如何工作。
短网址服务的基本流程:用户将长网址提交到短网址服务中,之后短网址服务经过URL处理之后,利用转换算法对长网址进行转换,最后分别将长网址和短网址存储到数据库之中。部分短网址服务为了防止出现对短地址进行连续转化或者提供一些展示长网址TITLE的功能,所以会对长网址进行访问。
其实对于短网址服务最核心的问题就是短网址的转换算法。那么常用的短网址算法有哪些呢?我们分析了GitHub上star数量最多的十个短网址服务对应的算法,大致分为三类:进制算法、随机数算法和HASH算法。
下面我利用简单的三个小例子介绍下对应的算法:
(1)进制算法:
算法简述:一个以数字、大小写字母共62个字符的任意进制的算法。
数据库中ID递增,当ID为233,则对应短网址计算过程如下:
- ①设置序列为“0123456789abcdefghijklmnopqrstuvwxyz”
- ② 233/36=6
- ③ 233%36= 17
- ④依次取上述字符的6位,17位,则为6h
其生成之后的短网址为xx.xx/6h
(2)随机数算法:
算法简述:每次对候选字符进行任意次随机位数选择,拼接之后检查是否重复
若要求位数为2,则其对应短地址为计算过程如下:
- ①设置字符序列“0123456789abcdefghijklmnopqrstuvwxyz”
- ②根据字符个数设置最大值为35,最小值为0,取2次随机数假设为:6,17
- ③依次取上述字符的6位和17位,则为6h
其生成之后的短网址为xx.xx/6h
(3)HASH算法:
算法简述:对id进行hash操作( 可选:利用随机数进行加盐),并检查是否重复
设置ID自增,若ID=233,则其对应短地址为计算过程如下:
- ①取随机数为盐
- ②对233进行sha1加密为: aaccb8bb2b4c442a7c16a9b209c9ff448c6c5f35:2
- ③要求位数为7,直接取上述加密结果的前7位为:aaccb8
其生成之后的短网址为xx.xx/2e8c027
了解完长网址转为短网址的流程之后,我们下边主要简单说明下短网址转化为长网址的流程,用户访问短网址,短网址服务返回一个302或者301的响应,从而跳转到长网址。这个地方,几乎所有短网址服务商会选择302,因为302方便统计和分析用户属性等数据。
二、短网址服务风险
由于短网址服务自身存在的设计缺陷问题,尤其是一般短网址采用6位或者7位字母和数字的集合,可以被很好的预测,从而被针对性的爆破。
而在爆破中最重要的一个步骤就是如何检测当前短网址使用的算法,从而生成该算法对应的字典,下边我们给出一些常见的算法检测过程:
1、进制算法
(1)第三方短网址服务
针对第三方的短网址服务,可以多次输入网址,查看返回短网址是否连续,连续则为进制算法,如下:
此外注意,由于个别为分布式短网址服务,id非单一递增,会出现多个字符规律变化,如:87BNwj、87BO82、87BOqw、87BOGz、87BPpD
(2)自营短网址服务:
对于自营短网址服务可以采用以下两个步骤进行,,
- ① 直接访问xx.xxx/1及xx.xxx/2低位等后缀,若均存在基本可以判定使用了进制算法进行转换。
- ② 对存在记录的后缀进行增加或减少尝试,若均存在记录或者规律间隔存在记录则基本认为使用了进制算法。
即:若某短网址存在http://xxx.xx/Abzc4 ,对Abzc4中最后一个单字符{0-Z}共62次变化。若均存在记录或存在a,c,e等有规律间隔情况,则同样可以认为使用了进制算法。
2、hash算法&随机数算法
(1)第三方短网址服务
对于第三方可以多次输入网址,查看返回短网址是否连续,不连续无规律则为HASH算法&随机数算法。如下图:
(2)自营短网址服务
- ① 直接访问xx.xxx/1及xx.xxx/2低位等后缀,若均不存在则进行步骤2。
- ② 对存在记录的后缀进行增加或减少尝试,若非均匀间隔存在记录则基本认为使用了进制算法。
即:若某短网址存在http://xxx.xx/Abzc4 ,对Abzc4中最后一个单字符{0-Z}共62次变化。若无明显规律则基本认为为HASH&随机数算法
接下来,我们分享一下短网址的两个攻击场景,第一个是由于部分短网址在传输过程使用了含有敏感权限和敏感信息的长网址,由此造成大量个人信息泄露:,第二个是由于短网址的可预测和可爆破,有时候可能会产生一些想象不到的效果。
案例一:爆破短网址服务获取大量服务、系统敏感信息:
1、获取个人信息
http://xx.xx/auth?contractId=d57f17139247036b72******b5554a830305ec139d
2、获取合同
https://xx.xx/get.action?transaction_id=290414****03784&msg_digest=RUQ2MUQ5NjcxQzc5MjcxQ*******4QTExNTZFNjgzQTJENEExQjc5Nw==
3、重置密码
https://xx.xx/resetPassword?emailType=RESET_PASSWORD&encryptionEmail=***GHOsR%2FMfiNEv8xOC29.&countersign=eyJhbGciOiJIUzUxMiJ9.eyJBQlNPTFVURV9FWFBJUkVfVElNRV9NSUxMUyI6IjE1M******zA1OTMxNjU4OTQiLCJORVdfRU1BSUwiOiJ5YW54aXUwNjE0QGdtYWlsLmNvbSIsIlRPS0VOX1RZUEUiOiJSRVNFVF9QQVNTV09SRCIsIkVNQUlMIjoieWFueGl1MDYxNEBnbWFpbC5jb20ifQ
其实单从上边的爆破出来的链接来看,每一个都极其的难以猜解,但是正是因为使用了短网址,从而把一些非常难猜解的高维度信息降低成了非常容易预测的低维度信息,就像你造了很坚固的房门,但是别人手里却有备用钥匙。
案例二:业务安全攻击链
- 邀请链接直接发送给邀请人,邀请人点击即可完成注册;
- 邀请链接以短网址发送;
- 批量邀请,爆破短网址,批量点击注册,即可完成薅羊毛;
某个应用有老用户邀请新用户的赚赏金活动,邀请链接以短网址形式发送给新用户,新用户点击链接之后,则赏金会放到老用户账户之中。那么在这个活动中,攻击者用户A可以随机选择两个手机号,我们分别用用户B和用户C来代替这两个用户,那么攻击用户A邀请随机选择的这两个手机号,之后直接爆破短网址进行确认,则在B和C不知情的情况下完成了赏金的领取。
三、短网址服务漏洞
其实当短网址出现短网址被猜解、爆破的问题,那么是不是会出现其他的问题,所以我们还对其进行了其他的安全测试。
1、SSRF安全问题
远程访问功能在过滤不严谨的情况下会造成SSRF,测试时使用自定义域名绑定一个内网地址之后进行访问,该短网址服务展示了长网址的TITLE,如下成功访问到了内网地址:
2、获取TITLE功能和展示长网址页面,在过滤不严谨的情况下,造成XSS。
部分短网址服务提供了长网址TITLE的展示功能和在当前页展示长网址的功能,在过滤不严谨的情况下也会造成xss。
如上图中cve为在展示长网址页面造成了xss问题。而同时取title并在页面上展示也会造成xss,比如可以构造payload:“
基本
文件
流程
错误
SQL
调试
- 请求信息 : 2026-02-16 19:43:15 HTTP/1.1 GET : /article/cdpdcsi.html
- 运行时间 : 0.0675s ( Load:0.0032s Init:0.0005s Exec:0.0597s Template:0.0041s )
- 吞吐率 : 14.81req/s
- 内存开销 : 2,235.05 kb
- 查询信息 : 12 queries 5 writes
- 文件加载 : 36
- 缓存信息 : 0 gets 2 writes
- 配置加载 : 130
- 会话信息 : SESSION_ID=ikv921clladjj4tl3a3p68ptm0
- /home/wwwroot/jxjierui.cn/index.php ( 1.12 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/ThinkPHP.php ( 4.61 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Think.class.php ( 12.26 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Storage.class.php ( 1.37 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Storage/Driver/File.class.php ( 3.52 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Mode/common.php ( 2.82 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Common/functions.php ( 53.56 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Hook.class.php ( 4.01 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/App.class.php ( 13.49 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Dispatcher.class.php ( 14.79 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Route.class.php ( 13.36 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Controller.class.php ( 11.23 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/View.class.php ( 7.59 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Behavior/BuildLiteBehavior.class.php ( 3.68 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php ( 3.88 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Behavior/ContentReplaceBehavior.class.php ( 1.91 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Conf/convention.php ( 11.15 KB )
- /home/wwwroot/jxjierui.cn/App/Common/Conf/config.php ( 2.12 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Lang/zh-cn.php ( 2.55 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Conf/debug.php ( 1.48 KB )
- /home/wwwroot/jxjierui.cn/App/Home/Conf/config.php ( 0.32 KB )
- /home/wwwroot/jxjierui.cn/App/Home/Common/function.php ( 3.33 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Behavior/ReadHtmlCacheBehavior.class.php ( 5.62 KB )
- /home/wwwroot/jxjierui.cn/App/Home/Controller/ArticleController.class.php ( 6.11 KB )
- /home/wwwroot/jxjierui.cn/App/Home/Controller/CommController.class.php ( 1.60 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Model.class.php ( 60.11 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Db.class.php ( 32.43 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Db/Driver/Pdo.class.php ( 16.74 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Cache.class.php ( 3.83 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Cache/Driver/File.class.php ( 5.87 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Template.class.php ( 28.16 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Template/TagLib/Cx.class.php ( 22.40 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Template/TagLib.class.php ( 9.16 KB )
- /home/wwwroot/jxjierui.cn/App/Runtime/Cache/Home/7540f392f42b28b481b30614275e4e55.php ( 13.96 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Behavior/WriteHtmlCacheBehavior.class.php ( 0.97 KB )
- /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Behavior/ShowPageTraceBehavior.class.php ( 5.24 KB )
- [ app_init ] --START--
- Run Behavior\BuildLiteBehavior [ RunTime:0.000005s ]
- [ app_init ] --END-- [ RunTime:0.000024s ]
- [ app_begin ] --START--
- Run Behavior\ReadHtmlCacheBehavior [ RunTime:0.000136s ]
- [ app_begin ] --END-- [ RunTime:0.000148s ]
- [ view_parse ] --START--
- [ template_filter ] --START--
- Run Behavior\ContentReplaceBehavior [ RunTime:0.000048s ]
- [ template_filter ] --END-- [ RunTime:0.000064s ]
- Run Behavior\ParseTemplateBehavior [ RunTime:0.003262s ]
- [ view_parse ] --END-- [ RunTime:0.003276s ]
- [ view_filter ] --START--
- Run Behavior\WriteHtmlCacheBehavior [ RunTime:0.000060s ]
- [ view_filter ] --END-- [ RunTime:0.000068s ]
- [ app_end ] --START--
- 1064: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 ') LIMIT 1' at line 1
[ SQL语句 ] : SELECT `id`,`pid`,`navname` FROM `cx_nav` WHERE ( id= ) LIMIT 1
- 1064: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 ') LIMIT 1' at line 1
[ SQL语句 ] : SELECT `id`,`navname` FROM `cx_nav` WHERE ( id= ) LIMIT 1
- 1064: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
[ SQL语句 ] : SELECT `id`,`navname` FROM `cx_nav` WHERE ( pid= )
- [8] Undefined index: pid /home/wwwroot/jxjierui.cn/App/Home/Controller/ArticleController.class.php 第 47 行.
- [2] file_put_contents(./App/Runtime/Temp/e9234ea637d8ea7ca037138f7b7f1813.php): failed to open stream: Permission denied /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Cache/Driver/File.class.php 第 132 行.
- [8] Undefined index: db_host /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Db.class.php 第 120 行.
- [8] Undefined index: db_port /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Db.class.php 第 121 行.
- [8] Undefined index: db_name /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Db.class.php 第 122 行.
- [2] file_put_contents(./App/Runtime/Temp/8173f328ff73b42401a558e50a61aa07.php): failed to open stream: Permission denied /home/wwwroot/jxjierui.cn/ThinkPHP/Library/Think/Cache/Driver/File.class.php 第 132 行.

0.0675s
