xss-labs-master通关心得
xss漏洞详解
XSS原称为CSS(Cross-Site Scripting),因为和层叠样式表(Cascading Style Sheets)重名,所以改称为XSS(X一般有未知的含义,还有扩展的含义)。XSS攻击涉及到三方:攻击者,用户,web server。用户是通过浏览器来访问web server上的网页,XSS攻击就是攻击者通过各种办法,在用户访问的网页中插入自己的脚本,让其在用户访问网页时在其浏览器中进行执行。攻击者通过插入的脚本的执行,来获得用户的信息,比如cookie,发送到攻击者自己的网站(跨站了)。所以称为跨站脚本攻击。XSS可以分为反射型XSS和持久性XSS,还有DOM Based XSS。(一句话,XSS就是在用户的浏览器中执行攻击者自己定制的脚本。)
常见的输出函数有: echo printf print print_r sprintf die var-dump var_export
.
存在的原因
XSS 存在的根本原因是,对URL中的参数,对用户输入提交给web server的内容,没有进行充分的过滤。如果我们能够在web程序中,对用户提交的URL中的参数,和提交的所有内容,进行充分的过滤,将所有的不合法的参数和输入内容过滤掉,那么就不会导致“在用户的浏览器中执行攻击者自己定制的脚本”。
但是,其实充分而完全的过滤,实际上是无法实现的。因为攻击者有各种各样的神奇的,你完全想象不到的方式来绕过服务器端的过滤,最典型的就是对URL和参数进行各种的编码,比如escape, encodeURI, encodeURIComponent, 16进制,10进制,8进制,来绕过XSS过滤。
xss漏洞分类
xss 分类:(三类)
-
反射型XSS:<非持久化> 攻击者事先制作好攻击链接, 需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。
-
存储型XSS:<持久化> 代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。
-
DOM型XSS:基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞。
开始通关
第一关
第一步查看源代码,发现该页面会显示我们通过get请求的变量name
可以看到这是用户输入的name变量,那么我们就猜测该位置会不会有XSS漏洞呢,也就是反射型的XSS,执行我们自己输入的前端代码。输入payload,过关,然后查看源代码,发现输入的payload已经嵌入到源代码中
http://localhost/xss-labs-master/level1.php?name=<script>alert('hack')</script>
说明源码是完全没有对GET请求到的name变量进行过滤,原封不动地发送到浏览器的,所以才会被执行。
第二关
第二关和第一关一样,首先观察值得注意的点,test,一是它的是以GET请求发送的变量,二是在页面上发现了用户发送的变量,查看源码
可以看到变量经服务器处理后又返回给了input标签的value中和<h2></h2>标签中,做题思路和第一关一样。修改GET发送的变量
显示无法通过,再去看源码
发现<h2>部分的恶意代码应该是被重新编码了,所以不会执行,而<input>标签内则是被闭包了。所以下面可以构造恶意代码,利用input的闭包,然后再利用//注释掉后面的 ">
构造的payload是
"> <script>alert("hack")</script> //
完成的不错!
第三关
第三关,查看源代码后和第二关差不多,姑且利用第二关的恶意url实验一下
发现h2和input处的<,>这样地敏感字符编码成了html字符实体。猜测服务器在这两处都用htmlspecialchars()函数进行了处理。
接下来就得想如何使得浏览器弹窗呢,利用input标签里的特殊事件,来执行js代码,例如onfocus语句,然后只需要闭合value的那个引号即可,注意,)后面需要加个空格,构造url
'onfocus=javascript:alert('xss')
发现页面并无多大变化,这是因为onfocus事件是JavaScript中对象获得焦点时发生的事件,最简单的实例就是网页上的一个搜索框,当鼠标点击该输入框,输入框被选中可以输入内容时就是该输入框获得焦点的时刻,此时输入框就会触发onfocus事件,因此点击当前页面输入框即可。
查看源码,发现和原来想的一样
第四关
利用简单的弹窗语句试一下
查看源代码发现
发现上面的h2将<的转义了,下面的value中的<被删除了,所以,再利用第四关的input标签的特殊事件,onfocus,,此处将单引号改为双引号构造url
"onfocus=javascript:alert('xss')
完成的不错!
第五关
通过之前的测试发现:
搜索框内并未对符号<,>进行操作,而是对一些关键字进行过滤比如_ 那我们可以再其中插入一个新的标签,先将前面的input标签给闭包,然后添加新的标签,构造如下:
"> <a href=javascript:alert('xss') > hack </a>
点击链接,完成的不错。
打开这关的源代码可以看到
可以看到在服务端的源码中,先对字符串转小写,再用字符串匹配对关键字进行替换。
第六关
经过之前的测试发现
再利用第五关的方法发现,
标签中的特殊字符都被过滤了,输入我们的测试语句试一下,发现关键字都被替换,而且在本例中href也被作为黑名单给屏蔽,那么有没有可能没有对字符串转小写呢,我们测试一下。
所以我们可以构造出下面的url:
"> <Script>alert('xss')</Script>
" Onfocus=javascript:alert('xss')
查看源码发现:
成功执行,Onfocus同理
第七关
按照以前的样子测试下
查看源代码发现
关键字被过滤了,可以猜测是将相应关键字替换成了空格,黑名单常用做法,所以可以双写绕过,构造如下:
"> <Sscriptcript>alert('xss')</sscriptcript> //
完成的不错!
查看服务端代码发现确实如此!
第八关
利用简单语句测试发现:
可以看到对我们输入语句的关键字script进行添加符号干扰,并转为了小写,而且猜测还对其进行htmlspecialchars()函数。
htmlspecialchars()是什么函数,也就是把预定义的字符转换为HTML实体
语法:
htmlspecialchars(string,flags,character-set,double_encode)
参数 |
描述 |
string |
必需。规定要转换的字符串。 |
flags |
可选。规定如何处理引号、无效的编码以及使用哪种文档类型。可用的引号类型:ENT_COMPAT - 默认。仅编码双引号。ENT_QUOTES - 编码双引号和单引号。ENT_NOQUOTES - 不编码任何引号。无效的编码:ENT_IGNORE - 忽略无效的编码,而不是让函数返回一个空的字符串。应尽量避免,因为这可能对安全性有影响。ENT_SUBSTITUTE - 把无效的编码替代成一个指定的带有 Unicode 替代字符 U+FFFD(UTF-8)或者 &#FFFD; 的字符,而不是返回一个空的字符串。ENT_DISALLOWED - 把指定文档类型中的无效代码点替代成 Unicode 替代字符 U+FFFD(UTF-8)或者 &#FFFD;。规定使用的文档类型的附加 flags:ENT_HTML401 - 默认。作为 HTML 4.01 处理代码。ENT_HTML5 - 作为 HTML 5 处理代码。ENT_XML1 - 作为 XML 1 处理代码。ENT_XHTML - 作为 XHTML 处理代码。 |
character-set |
可选。一个规定了要使用的字符集的字符串。允许的值:UTF-8 - 默认。ASCII 兼容多字节的 8 位 UnicodeISO-8859-1 - 西欧ISO-8859-15 - 西欧(加入欧元符号 + ISO-8859-1 中丢失的法语和芬兰语字母)cp866 - DOS 专用 Cyrillic 字符集cp1251 - Windows 专用 Cyrillic 字符集cp1252 - Windows 专用西欧字符集KOI8-R - 俄语BIG5 - 繁体中文,主要在台湾使用GB2312 - 简体中文,国家标准字符集BIG5-HKSCS - 带香港扩展的 Big5Shift_JIS - 日语EUC-JP - 日语MacRoman - Mac 操作系统使用的字符集注释:在 PHP 5.4 之前的版本,无法被识别的字符集将被忽略并由 ISO-8859-1 替代。自 PHP 5.4 起,无法被识别的字符集将被忽略并由 UTF-8 替代。 |
double_encode |
可选。布尔值,规定了是否编码已存在的 HTML 实体。TRUE - 默认。将对每个实体进行转换。FALSE - 不会对已存在的 HTML 实体进行编码。 |
我们可以将js的代码转换为Unicode的编码进行绕过
javascript:alert('xss')
或
javasc%26%2382;ipt:alert(1)
第九关
利用上一题的语句进行测试发现
查看源代码发现:
这里加了一个判断,利用strpos函数对输入语句进行判断。
strpos()用于查找指定字符串第一次出现的位置,返回整数型。也就是本题就是判断字符"http://"
是否在字符串中出现。那我们在keyword变量的最后加上“http://”并将其注释掉即可。
语句如下:
javascRipt:alert(1)//http://
完成的不错!
第十关
可以发现不再有搜索框,也不再有超链接可以给我们点了。常规语句也不行。
查看前端代码发现有几个参数,猜测这几个参数会不会也会被服务器端给接收呢
构造语句试着传输一下这3个参数发现
http://localhost/xss-labs-master/level10.php?keyword=well%20done!&t_link1&t_history=2&t_sort=3
发现:
发现服务端接受了t_sort传来的参数,利用这一点尝试注入,构造一下:
keyword=well%20done!&t_sort=“> <script>alert('xss')</script>//
发现尖括号都被过滤了,但是关键字就没有,那就使用一些能在尖括号内使用的函数。
尝试使用input内的函数onfocus
" onfocus=javascript:alert('xss')%20
发现没什么反应,这个与函数不大行,不能触发,因为没有可以聚焦的组件。
所以这个时候转换思路,我们改变其type,使其显露出来,再用onclick来触发。点击一下即可触发事件。
" type="text" onclick="alert('xss')"
点击后发现
完成的不错!
第十一关
查看前端代码发现多了一个参数
其字段名为ref,联想到http报文中的referer字段。再刷新一次,发现果然,是会根据服务器接收的值动态变化的。
构造url发现:
keyword=good%20job!&t_link=1&t_history=2&t_sort=3
本关应该从ref这下手,构造语句,然后利用hackbar讲参数传入
" type="text" onclick="alert('xss')"
然后点击网页上出现的框,完成的不错!
第十二关
查看前端代码发现:t_ua
上一题中的ref变为了ua,参数,http报文中的user agent参数段
按照上一题中的方法,再试一下,构造语句:
" type="text" onclick="alert('xss')"
执行后页面出现边框,点击后完成
第十三关
观察前端代码发现
可以看到参数又变了,所以在hackbar里传入到cookie里
构造注入语句:
user=" type="text" onclick="alert('xss')
执行后,完成的不错
第十四关
本关有点问题,略过
第十五关
查看前端代码发现:
发现了一个新属性,ng-include
,那么这个东西有何作用?ng-include指令一般用于包含外部HTML文件,ng-include属性的值可以是一个表达式,返回一个文件名,但是默认情况下,包含的文件需要包含在同一域名下,也就是要调用同一域名下的其他网页
?src='level1.php?name=<img src=1 onerror=alert(1)>'
给语句使本页面包含了同域名level1.php返回的HTML文件,并将name的字段传入,虽然尖括号被编码了,但是level1的name处是没有任何括号之内的东西的,所以可行。
输入语句后,前端代码
完成的不错!
第十六关
来到第十六关,发现了
尝试简单注入
"> <script>alert('xss')</script>//
查看前端代码发现
关键字script
以及 /
和空格
都被编码成同样的空格字符实体了。
但是在html中是可以将%0a和%0d作为空格使用的
因为尖括号没被干掉嘛我们就用<a>标签,因此我们构造语句
<a%0dhref='javas%0acript:alert("hack")'>xss
点击xss,完成的不错
第十七关
第十七关打开看到url栏目显示
查看前端代码
发现其两个变量使用=
符号拼接的形式被加入到<embed>标签中,该标签用于一些插件如flash等的插入,那这就是一个突破口
常规加尖括号的语句会被转为html实体字符,那就只有用事件触发来写,而且事件触发刚好是用等号来连接。
所以我们在b的后边加入onclick(点击后触发)或则onmouseover(鼠标移动到上方触发)触发器来进行恶意语句利用
?arg01=a&arg02=b onmouseover=alert('hack')
使用语句
但是在火狐浏览器中,发现该组件根本不显示,完全不给我们事件触发的机会,无法利用了,那怎么办呢。浏览资料发现有些浏览器会将无用组件直接屏蔽,但有些不会。所以我们转到edge试一下。
完成的不错!
第十八关
因为浏览器的缘故
火狐无法显示换成Google或edge
查看前端代码发现
还是会传入两个参数
利用上一题中的语句,进行实验
?arg01=a&arg02=b onmouseover=alert('hack')
和上一题几乎一致没什么变化。完成的不错
第十九关
17、18、19、20都是有关flash xss的
太难了,不会做啊!!!
第二十关
太难了,不会做啊!!!
关于xss漏洞的防御
XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码。
也就是对提交的所有内容进行过滤,对url中的参数进行过滤,过滤掉会导致脚本执行的相关内容;然后对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的XSS攻击。
1 对输入和URL参数进行过滤(白名单和黑名单)
下面贴出一个常用的XSS filter的实现代码:
public class XssFilter implements Filter {
public void init(FilterConfig config) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest)request);
chain.doFilter(xssRequest, response);
}
public void destroy() {}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
/**
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/>
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
* getHeaderNames 也可能需要覆盖
*/
@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
/**
* 将容易引起xss漏洞的半角字符直接替换成全角字符
*
* @param s
* @return
*/
private static String xssEncode(String s) {
if (s == null || s.isEmpty()) {
return s;
}
StringBuilder sb = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '>':
sb.append('>');// 全角大于号
break;
case '<':
sb.append('<');// 全角小于号
break;
case '\'':
sb.append('‘');// 全角单引号
break;
case '\"':
sb.append('“');// 全角双引号
break;
case '&':
sb.append('&');// 全角
break;
case '\\':
sb.append('\');// 全角斜线
break;
case '#':
sb.append('#');// 全角井号
break;
case '%': // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
processUrlEncoder(sb, s, i);
break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
public static void processUrlEncoder(StringBuilder sb, String s, int index){
if(s.length() >= index + 2){
if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'c' || s.charAt(index+2) == 'C')){ // %3c, %3C
sb.append('<');
return;
}
if(s.charAt(index+1) == '6' && s.charAt(index+2) == '0'){ // %3c (0x3c=60)
sb.append('<');
return;
}
if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'e' || s.charAt(index+2) == 'E')){ // %3e, %3E
sb.append('>');
return;
}
if(s.charAt(index+1) == '6' && s.charAt(index+2) == '2'){ // %3e (0x3e=62)
sb.append('>');
return;
}
}
sb.append(s.charAt(index));
}
/**
* 获取最原始的request
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取最原始的request的静态方法
*
* @return
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
}
然后在web.xml中配置该filter:
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.xxxxxx.filter.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
主要的思路就是将容易导致XSS攻击的边角字符替换成全角字符。< 和 > 是脚本执行和各种html标签需要的,比如 <script>,& 和 # 以及 % 在对URL编码试图绕过XSS filter时,会出现。我们说对输入的过滤分为白名单和黑名单。上面的XSS filter就是一种黑名单的过滤,黑名单就是列出不能出现的对象的清单,一旦出现就进行处理。还有一种白名单的过滤,白名单就是列出可被接受的内容,比如规定所有的输入只能是“大小写的26个英文字母和10个数字,还有-和_”,所有其他的输入都是非法的,会被抛弃掉。很显然如此严格的白名单是可以100%拦截所有的XSS攻击的。但是现实情况一般是不能进行如此严格的白名单过滤的。
对于输入,处理使用XSS filter之外,对于每一个输入,在客户端和服务器端还要进行各种验证,验证是否合法字符,长度是否合法,格式是否正确。在客户端和服务端都要进行验证,因为客户端的验证很容易被绕过。其实这种验证也分为了黑名单和白名单。黑名单的验证就是不能出现某些字符,白名单的验证就是只能出现某些字符。尽量使用白名单。
2 对输出进行编码
在输出数据之前对潜在的威胁的字符进行编码、转义是防御XSS攻击十分有效的措施。如果使用好的话,理论上是可以防御住所有的XSS攻击的。
对所有要动态输出到页面的内容,通通进行相关的编码和转义。当然转义是按照其输出的上下文环境来决定如何转义的。
1> 作为body文本输出,作为html标签的属性输出:
比如:${username}, <p><c:out value="${username}">/c:out</p>
<input type="text" value="${username}" />
此时的转义规则如下:
< 转成 <
> 转成 >
& 转成 &
" 转成 "
' 转成 '
2> javascript事件
<input type="button" οnclick='go_to_url("${myUrl}");' />
除了上面的那些转义之外,还要附加上下面的转义:
\ 转成 \
/ 转成 \/
; 转成 ;(全角;)
3> URL属性
如果 <script>, <style>, <imt> 等标签的 src 和 href 属性值为动态内容,那么要确保这些url没有执行恶意连接。
确保:href 和 src 的值必须以 http://开头,白名单方式;不能有10进制和16进制编码字符。
3 HttpOnly 与 XSS防御
XSS 一般利用js脚步读取用户浏览器中的Cookie,而如果在服务器端对 Cookie 设置了HttpOnly 属性,那么js脚本就不能读取到cookie,但是浏览器还是能够正常使用cookie。
一般的Cookie都是从document对象中获得的,现在浏览器在设置 Cookie的时候一般都接受一个叫做HttpOnly的参数,跟domain等其他参数一样,一旦这个HttpOnly被设置,你在浏览器的 document对象中就看不到Cookie了,而浏览器在浏览的时候不受任何影响,因为Cookie会被放在浏览器头中发送出去(包括ajax的时 候),应用程序也一般不会在js里操作这些敏感Cookie的,对于一些敏感的Cookie我们采用HttpOnly,对于一些需要在应用程序中用js操作的cookie我们就不予设置,这样就保障了Cookie信息的安全也保证了应用。
如果你正在使用的是兼容 Java EE 6.0 的容器,如 Tomcat 7,那么 Cookie 类已经有了 setHttpOnly 的方法来使用 HttpOnly 的 Cookie 属性了。
cookie.setHttpOnly(``true``);
设置完后生成的 Cookie 就会在最后多了一个 ;HttpOnly
另外使用 Session 的话 jsessionid 这个 Cookie 可通过在 Context 中使用 useHttpOnly 配置来启用 HttpOnly,例如:
<``Context` `path``=``""` `docBase``=``"D:/WORKDIR/oschina/webapp"`` ``reloadable``=``"false"` `useHttpOnly``=``"true"``/>
也可以在 web.xml 配置如下:
<``session-config``>`` ``<``cookie-config``>`` ``<``http-only``>true`http-only``>`` ```cookie-config``>``<``session-config``>
对于 不支持 HttpOnly 的低版本java ee,可以手动设置(比如在一个过滤器中):
String sessionid = request.getSession().getId();
response.setHeader("SET-COOKIE", "JSESSIONID=" + sessionid + "; HttpOnly");
对于 .NET 2.0 应用可以在 web.config 的 system.web/httpCookies 元素使用如下配置来启用 HttpOnly?
<``httpCookies` `httpOnlyCookies``=``"true"` `…>
而程序的处理方式如下:
C#:
HttpCookie myCookie = ``new` `HttpCookie(``"myCookie"``);``myCookie.HttpOnly = ``true``;``Response.AppendCookie(myCookie);
VB.NET:
Dim` `myCookie ``As` `HttpCookie = new HttpCookie(``"myCookie"``)``myCookie.HttpOnly = ``True``Response.AppendCookie(myCookie)
.NET 1.1 只能手工处理:
Response.Cookies[cookie].Path += ``";HttpOnly"``;
PHP 从 5.2.0 版本开始就支持 HttpOnly
session.cookie_httponly = True
PS: 实际测试在 Tomcat 8.0.21 中在 web.xml 和 context中设置 HttpOnly 属性不起作用。只有cookie.setHttpOnly(true); 和response.setHeader("SET-COOKIE", "JSESSIONID=" + sessionid+ "; HttpOnly"); 起作用。
4 总结
XSS攻击访问方法:对输入(和URL参数)进行过滤,对输出进行编码;白名单和黑名单结合;