DVWA详解:JavaScript篇(全)
前言:
关于"JavaScript"篇,话说当年博主在玩DVWA时,是没有研究这个内容的,当年博主玩DVWA,很多时候还是参考了网络上的教程去学的,而最近又看到了这个内容,偶然间突发其想,自己是不是有能力,在不参考网络教程的情况下,基于现在博主所掌握的知识,去破解DVWA的这个漏洞;结果还是比较好的,博主的确可以在不参考网络教程的情况下,基于博主自己所掌握的知识,在并没有花多少时间的前题下,解决这个"JavaScript"的漏洞题目;
话虽然是这么说,但解决这"JavaScript"的漏洞题目,并没有给博主带来多少惊喜;博主原来以为这是基于"JavaScript"代码漏洞的一次主机入侵过程,但可惜这不是!!!严格来说,这一题目甚至与"漏洞"这个话题没什么关系。更多的情况下,博主认为DVWA设计这一个所谓的"漏洞",其目标更倾向于告诉渗透人员,服务器上,所有页面上的"JavaScript"代码,是暴露在客户端侧的,不安全的"JavaScript"代码可能会引发意外的"漏洞",毕竟,所有人都能看到你的"JavaScript"代码;
实际上,"JavaScript"篇的关键点是如何"获取/计算"出需要的"TOKEN";如果去了解该题目的页面,从页面的代码逻辑上来说,这本身就是一个错误的页面,因为现实中线上的环境,就不应该出现这样的WEB页面,这也是不博主不太喜欢这个题目的原因;但无论如何,DVWA官方想要表达的"不安全的JavaScript代码可能会引发意外的漏洞",也许,使用这样的方式算是比较容易实现的一种方法;
博主在写这篇博文时,是有考虑过是不是有需要写关于"JavaScript"这个内容的解题博文的,于是百度了一下该内容,看了百度推荐在前面的几篇博文,结果还是不太尽我意,解题思路是没什么问题的,只是看上去有点复杂或不清楚;不少博文使用的"BurpSuite",嗯...,有这个必要嘛~~使用任意浏览器就可以了啊,完全没必要上"BurpSuite"...
特别提示:如果你在实验过程中,直接复制本博客任意博文中的配置/代码,"空格"字符的前面将可能产生不可见的字符"M-BM-",从而造成文件不可用,这是字符编码的问题~若必需使用复制粘贴方式,请务必手动替换掉配置项中的所有空格!!!或尝试使用以下命令过滤掉所有"M-BM-"字符~
1 2 3 4 |
sed -i 's/\xc2\xa0/ /g' [文件名称] # LINUX下可使用以下命令查看不可见字符的情况 cat -A [文件名称] |
环境需求:
测试需要部署DVWA环境,如果你会DOCKER,可以使用博主提供的文件快速部署一个可用的DVWA环境:下载"DVWA-2.3.tar.gz"压缩包,通过DOCKER-COMPOSE快速部署起DVWA的2.3版本:百度网盘(提取码:7s4q)--> [ 文件名称:DVWA/DVWA-2.3.tar.gz ];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 注:在每次使用"docker-compose down"完全清除容器后,再次运行需要使用"./Readme.sh"文件(有配置文件复制至容器); $ tar -zxf DVWA-2.3.tar.gz $ cd DVWA $ ./Readme.sh # 其它命令 $ docker-compose down # 删除DVWA服务(再次创建服务需要使用"Readme.sh"脚本) $ docker-compose stop # 停止DVWA服务(在不删除DVWA容器时用本命令停止即可) $ docker-compose start # 启动DVWA服务(在不删除DVWA容器时用本命令启动即可) # ================================================================================================= # 其它补充: # 你可能在博文的截图中看到博主使用了"dvwa.domain.local"的域名,这是博主在浏览器的主机上使用了自定义的HOSTS解释; # > LINUX :上,你可以在"/etc/hosts"文件中自定义DNS解释 # > WINDOW :你可以在"c:\windows\system32\drivers\etc\hosts"文件中自定义DNS解释 |
使用博主的代码搭建DVWA环境的大概截图过程如下:
LOW - 源码分析:
要解题DVWA的JavaScript题目,应该先从源代码入手,因为DVWA中这个所谓的"JavaScript"漏洞,想要告诉你的是"JavaScript的代码完全暴露在客户侧,不安全的JavaScript代码可能引发意外的漏洞";正常情况下,我们应该是使用浏览器的开发者工具,去查看HTML页面的相关的JavaScript代码,但既然DVWA中可以直接查看,那么我们直接查看就可以了,因为两种方式本质上并没有任何区别;下面是LOW等级的源代码解读:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php $page[ 'body' ] .= <<<EOF <script> // 基于"JavaScript"的MD5算法的实现(这部分内容太长了,博文使用"..."省略代替了原内容) !function(n){"use strict";...}(this); // 基于"JavaScript"的ROT13算法的实现 // ROT13是一种简单的替换加密算法(它将每个字母向后移动13个位置) // > 如:'A'变成'N','B'变成'O',依此类推. // > 题外话,由于字母刚好是26个,所以运行两次ROT13算法结果会是原字符串 function rot13(inp) { return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);}); } // 自定义的"generate_token()"的代码 // 获取"document.getElementById("phrase").value"的值然后先进行"rot13()"运算再进行"md5()"运算 // 然后将结果赋值给"document.getElementById("token").value" function generate_token() { var phrase = document.getElementById("phrase").value; document.getElementById("token").value = md5(rot13(phrase)); } // HTML页面加载后,自动应用"generate_token()"; // // 直接提交"success"失败的原因在这里,按浏览器的执行顺序,其在浏览器的响应请求返回后,就会执行本函数, // 而此时的"document.getElementById("phrase").value"的值被固定为"ChangeMe",而非所需要的"success", // 所以当你在输入框填写"success"并提交后,会返回"Invalid token." generate_token(); </script> EOF; ?> |
LOW - 实验操作:
在任意浏览器中,访问"JavaScript Attacks"页面,在文本框填入"success"并提交,结果返回"Invalid token.",提示TOKEN错误(表明"success"的提交失败);
按F12打开发者工具,访问"网络"项,发现请求所提交的参数中,是有提交"token"参数的,这表明了这个"token"的值是错误的(基于开发者工具的工作原理,你需要重新执行上一步的操作来刷新出数据);在这一步,你可以尝试在输入框中填写队"success"之外的任意字符串,可以发现"token"的值是不会变的,这说明用于计算"token"的原始字符串是一个"固定值",现在需要的是找出这个"固定值"到底是什么?
访问开发者工具中的"元素"项,审查页面内容,可以发现表单提交的"token"参数的值在页面中就存在了,结合JavaScript的源代码,我们可以发现这个"token"的值是通过"ChangeMe"这个固定字符串通过"md5(rot13())"方法计算出来的;到这里,我们就应该可以大概推测出我们提交"success"失败的原因了:页面要求提交的是"success",但计算TOKEN时却使用了"ChangeMe"字符串作为原始值,正常情况下,一般应该使用"success"作为原始值去计算出TOKEN的值,然后再用这个值去提交请求(注意,这里是猜测,实际的TOKEN需要值是什么还需要验证,但从源代码的逻辑角度去分析,这是非常有可能成功的解题方法);
在上一步中,我们推测提交"success"的可能原因,现在我们需要在这一步中验证我们的想法;要验证我们的推测,就是要"修正页面中的TOKEN的值",在这里有两种方法:
A、将元素页面中的"ChangeMe"修改为"success",然后再在控制台中重新执行"generate_token()"方法更新TOKEN的值;这种方式更便于理解,而且也不需要真正的了解LOW等级中JavaScript源代码的逻辑原理;
B、在控制台中使用"md5(rot13('success'))"计算出正确的TOKEN,然后直接在元素页面中修改TOKEN的值;这种方式更方便直接,但需要你对LOW等级的JavaScript源代码有更深入的理解;另外,既然我们知道了TOKEN的计算原理,直接使用LINUX的命令行来计算也是可以的($ echo -n "success" | tr 'A-Za-z' 'N-ZA-Mn-za-m' | md5sum),并不一定需要开发者工具中的控制台;
在使用上面两种方式中的一种修正页面中的TOKEN后,返回浏览器,再次填写"success"进行提交,这一次返回"Well done!",表明提交成功,也验证了我们之前的猜测。
MEDIUM - 源码分析:
MEDIUM等级的JavaScript源码,博主感觉比LOW等级还简单,有点扰乱你的是"代码的编写顺序",反正正常人不会使用这种代码的顺序写法;另外,其在HTML页面中使用了JS文件引入的方式来导入JavaScript代码,看上去比LOW等级更隐蔽一点而已;综合来说,"JavaScript Attacks"页面的TOKEN的值来源于JavaScript代码中的"do_elsesomething("XX")"函数的运行结果,代码解释见下;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 函数"do_something(e)"用于反转字符串 function do_something(e) { for (var t = "", n = e.length - 1; n >= 0; n--)t += e[n]; return t } // 设定延迟300毫秒后执行"do_elsesomething("XX")"(这里传参"XX") // 延迟300毫秒的意义在于使浏览器能正确加载后面的"do_elsesomething(e)"代码,这本来可以通过代码顺序解决 setTimeout( function () { do_elsesomething("XX") }, 300 ); // 函数"do_elsesomething(e)"用于拼接字符串 // 例如:执行"do_elsesomething("XX")",并且假设"document.getElementById("phrase").value"的值为"ChangeMe", // 那么"document.getElementById("token").value"将被设定为"XXeMegnahCXX" function do_elsesomething(e) { document.getElementById("token").value = do_something(e + document.getElementById("phrase").value + "XX") } |
MEDIUM - 实验操作:
MEDIUM等级中对TOKEN分析过程基本上与LOW等级相同,关于分析过程这里就不重复说明和截图了,参考LOW等级即可;对于解题方法,同样的需要"修正MEDIUM等级页面中的TOKEN的值",一样有两种方法(和LOW类似):
A、将元素页面中的"ChangeMe"修改为"success",然后再在控制台中重新执行"do_elsesomething('XX')"方法更新TOKEN的值;这种方式遵从JS代码的正常逻辑;
B、直接将元素页面中TOKEN的值由"XXeMegnahCXX"修改为"XXsseccusXX";这种方式更方便直接,因为很简单的就能看出"eMegnahC"是"ChangeMe"的反转,自然的,我们也只需要将"success"反转再填入即可,这种方式有点取巧;当然我们知道了TOKEN的计算原理,也可直接使用LINUX的命令行来计算也是可以的($ echo "success" | sed 's/^/XX/' | sed 's/$/XX/' | rev);
在使用上面两种方式中的一种修正页面中的TOKEN后,返回浏览器,再次填写"success"进行提交,这一次返回"Well done!",表明提交成功,也验证了我们之前的猜测,另外这里就不符截图了,和LOW等级看上去没区别;
HIGH - 源码分析:
HIGH等级的JavaScript源码,相比于"LOW/MEDIUM"等级,有了一些比较明显的区别:1、使用JS代码的混淆技术,造成JS源码难于阅读;2、使用了一句'document.getElementById("phrase").value = ""'的JS代码,使HIGH难度无法像"LOW/MEDIUM"难度一样直接在元素页面将"ChangeMe"修改成"success"来更新TOKEN(有其它方法更新TOKEN);
关于第"1"点,虽然博主知道混淆技术的存在,但是博主并没有认真的统一归纳去理解这个内容,刚刚开始时的确没办法入手,毕竟混淆技术应该也有很多种,进行反混淆操作的前提,应该是需要知道其原始所使用的混淆技术才行;博主也没什么心情去一一验证,于是通过官方的提示文档来入手的。官方提示了可以使用"http://deobfuscatejavascript.com/"来对HIGH等级的JS源码进行反混淆,博主也是遵从这一个操作;
由于HIGH等级的JS代码在混淆技术的处理下难以阅读,本博文使用了经过"http://deobfuscatejavascript.com/"进行过反混淆的JS代码来进行解释,解释结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// 省略前面的部分,省略部分的主要内容是一些"哈希算法"的实现 ... // 函数"do_something(e)"的功能是将字符串反转 function do_something(e) { for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n]; return t } // 获取"document.getElementById("token").value"的值然后在后面拼接"ZZ"后进行SHA256运算后, // 再重新赋值给"document.getElementById("token").value" function token_part_3(t, y = "ZZ") { document.getElementById("token").value = sha256(document.getElementById("token").value + y) } // 获取"document.getElementById("token").value"的值然后在前面拼接"YY"后进行SHA256运算后, // 再重新赋值给"document.getElementById("token").value" // 注意"e"参数的默认值是"YY",但这个值是可以通过传参来改变的; function token_part_2(e = "YY") { document.getElementById("token").value = sha256(e + document.getElementById("token").value) } // 调用"do_something(document.getElementById("phrase").value)"再赋值给"document.getElementById("token").value", // 虽然这个函数有传参设定,但这些传参压根没有使用,这可能是混淆代码加入的无用传参代码; function token_part_1(a, b) { document.getElementById("token").value = do_something(document.getElementById("phrase").value) } // 这行代码将"document.getElementById("phrase").value"的值设定为空,就是这行代码,使HTML中的TOKEN计算出来的值永远相同; // > 如果没有这行代码,"document.getElementById("phrase").value"的值应该是"ChangeMe"; // > 这也是为什么即使你将"ChangeMe"修改为"success"依然无法改变TOKEN的计算值; document.getElementById("phrase").value = ""; // 延迟300毫秒后调用"token_part_2("XX")"并传参"XX"替换默认值"YY" setTimeout(function() { token_part_2("XX") }, 300); // 在点击时自动调用"token_part_3" // 这是在页面中点击提交后自动触发执行"token_part_3()"运算,这也就是为什么在计算HTML页面上的TOKEN值时, // 只需考虑"token_part_1()"与"token_part_2()",不需要考虑"token_part_3()"的原因; document.getElementById("send").addEventListener("click", token_part_3); // 自动执行"token_part_1"(传入的参数无意义) token_part_1("ABCD", 44); |
关于第"2"点,博主觉得还是有点意思的,所以特别再多写一下;起初博主并未特别关注'document.getElementById("phrase").value = ""'这行代码,还是按照"LOW/MEDIUM"时的方法,修改页面中的"ChangeMe"为"success"来尝试更新TOKEN,当然失败了;后来考虑了JS在浏览器上运行的先后顺,上了"BurpSuite"拦截"响应数据",强行将"响应数据"中的"ChangeMe"替换为"success",再将"响应数据"返回至浏览器,发现TOKEN依然没有更新?再再再,博主尝提交除"success"之外的任意其它"字符串",发现TOKEN依然不变!!!
此时博主产生了疑问,TOKEN的值与提交的"字符串"没有关联?细心想想,这不可能啊,如果是没有关联,那这个TOKEN将变没有意义了!!!一定是博主忽略了什么!!!于是,关注到了'document.getElementById("phrase").value = ""'这行代码,原来是在HTML页面加载完成后,JS强行将'document.getElementById("phrase").value'设定为了空值...(正常情况下这个值应该是"ChangeMe");于是,博主也就理解了页面中的TOKEN为什么不变,和这个原始的TOKEN的值是怎么来的~~下面是关TOKEN原始结果一些截图解释:
HIGH - 实验操作:
HIGH等级中对TOKEN分析过程见上面源代码中关于'document.getElementById("phrase").value = ""'这行代码的分析,这是主要要点;对于HIGH难度中如何"修正页面中的TOKEN的值",一样有两种方法:
A、在控制台中重新为"document.getElementById("phrase").value"赋值"success",然后按顺序执行"token_part_1()"与"token_part_2('XX')"方法更新页面中TOKEN的值;
B、直接将元素页面中TOKEN的值修改成正确的TOKEN值;当然,要实现这个目标,需要我正确的理解HIGH等级中的JS代码,然后我们可以使用其它工具计算出这个TOKEN的结果,例如使用LINUX命令($ echo -n "XX$(echo -n "success" | rev)" | sha256sum 或 $ echo -n "success" | rev | sed 's/^/XX/' | sha256sum);
在使用上面两种方式中的一种修正页面中的TOKEN后,返回浏览器,再次填写"success"进行提交,这一次返回"Well done!",表明提交成功(本处也不截图了,一样的);
最后,简单解释一下HIGH难度下,点击"提交"按键后,提交参数中"token"的值不同于HTML页面上TOKEN的值定义的问题;这主要是由于在点击"提交"按键后,针对HTML页面中的TOKEN的值还会进行一次额外的"token_part_3()"运算,要想得到提交参数中"token"的值,可以使用以下LINUX命令行计算得出:$ echo -n "success" | rev | sed 's/^/XX/' | sha256sum | awk '{printf $1}' | sed 's/$/ZZ/' | sha256sum ;
结:
至此,本篇关于DVWA上JavaScript篇上三个难度已经解释完成了;这个DVWA篇章主要的目的就是告诉你:"JavaScript"代码完全暴露在客户端侧,不安全的"JavaScript"代码可能外发意外的入侵漏洞!!!只要读者明白了以上的关键点,DVWA的"JavaScript"章节就完成其目标了~~~
DVWA详解:JavaScript篇(全):等您坐沙发呢!