我们知道网络无时无刻不在受着威胁。当一些恶意脚本被上传到服务器上时,网站就会面临被侵入的威胁。然而上传恶意脚本并不容易,因为文本形式的东西,我们很好设置过滤条件,把认为有威胁的文本过滤掉,不让它上传到服务器上,这样就能有效的避免很多不必要的麻烦。
举个例子,看眼前。
我写了这篇文章,编辑,保存,发布。这篇文章就被保存进了CSDN的数据库里。当你们想查看这篇文,去请求服务器时,服务器就会从数据库里调这篇文章发给你。
试想我在文章里写了这么一句话:
<script>alert("This is a joke")</script>
如果服务器不加处理的就把它保存进了数据库,会怎样?
你们每个点开的人的页面上都会弹出“This is a joke”的消息框。那不就乱了套了!
那浏览器是怎样把上面的那句话解释成文本显示出来,而没有把它理解成标签去执行呢?
你现在就可以右键查看源码,看看源码里面,那句脚本被表示成了什么样子。(快捷键ctrl + u)
这其实就算一种对文本的过滤,服务器不会把你的东西原封不动的保存在数据库里,要把你的文本过滤过滤,把可能有问题的东西修改掉,或者直接丢弃。因为,你不值得信任,无论是因为蠢,写错了;还是太聪明,故意的。都不会!
下面的评论也是一样,服务器不会把内容原封不动的存起来。
接下来的这篇文章就来谈另一种上传漏洞。
文本你过滤,那我换图片喽!攻防的交换就是如此,在斗争中不停地进步。
我拿这张图片做测试。
这是张jpg格式的图片,我先把它转成了bmp格式。倒没有什么特殊的原因,bmp的文件结构相对其他的来说比较简单。如果你对其他图片格式的文件结构了解颇深的话,不需要做多余的操作。往下走就行,原理都是一样的。
你可以通过mspaint另存为bmp格式,或者通过python的PIL库,亦干脆用工具转换都可以。
我没办法把处理好的图片上传上来,原因你懂得。因为它有问题,服务器检测出来了。
先来分析下图片,用二进制形式读取图片输出一部分看看:
前两个字节是BM(因为这张图片是处理过的,所以bm后面有/*,先忽略掉它),有的地方把它叫做“魔数”。摘段百度百科的解释:
如果你有可以打开图片的编辑器,比如notpad++之类的。你就会看到一堆16进制表示的数字。python解释器用文本的方式解释了二进制文件,所以我们看到了BM(其实就是输出了它的ASCII码对应的字符)。在编辑器里,用十六进制表示的话是42 4D。
(拿十六进制的42来说,二进制就是 0100 0010,这里只是拿十六进制的方式显示了出来。)
魔数用于识别文件,换句话说只要保留住BM,管它后面是什么东西,都能被识别为bmp图片。
而你需要做的是,保留BM,把之后的东西用/* */注释掉,再在图片最后插入脚本。当然所有的插入,都需要以二进制的格式。
至于为什么中间注释的东西还要留着而不是去掉呢?嗯......因为它毕竟是张图片,图片显示的时候是一个个的像素点,没有这些东西,图片也就显示不出来了。虽然对你的脚本没什么影响。
这里有个问题,比如原来的表示是
BM\x00\x00\x00\x00\x00......,你现在在BM后面插入了/*,十六进制表示是\x2F\x2A。在IDLE里显示一下就可以了:
那现在的表示就变成了,BM\x2F\x2A\x00\x00\x00\x00\x00。
显示图片时,原来是\x00是第一个像素点,然后显示第二个像素点,再之后是第三个。
现在处理完后,所有的像素点显示都向后推了两个点。那图片的显示不就乱套了?
的确会有出入,但是整体的图片还是可以显示的。(以上的例子只是说明,其实BM之后的字节并不是显示的像素点,而是文件的大小,这里只是说明问题。)
我把原图和处理完的图片贴出来,对比下。
处理完毕的图片显然显示上有些小瑕疵。(PS:第二张图是截图,不是处理后的图片原图。原因说了,服务器不要。)
那就来看这张“有料”的图片是怎么做出来的吧(语言:python)。(虽然原理已经讲得很清楚了。)
#read data of the picture
with open(fname, 'r+b') as f:
buff = f.read()
#replace * and \ in buff
buff = buff.replace(b'\x2A', b'\x00').replace(b'\x2F', b'\x00')
先把图片以二进制的形式读出来,然后替换。前面说了十六进制的\x2A是*,\x2F是/。
为什么要替换掉呢。其实是因为在js里面/*是注释嘛,只是怕图片的像素点表示恰好和它相同了,导致最后文件出错,脚本执行不了。
with open(fname, 'w+b') as f:
f.write(buff)
f.seek(2, 0)
f.write(b'\x2F\x2A')
之后再次打开图片文件把替换后的内容写进去。(覆盖掉了原文件)
seek不需要过多解释了吧,默认你有python基础。因为我们要留下bm来识别图片,所以文件指针后移2字节。没有第二个参数也可以(默认的)。
然后填入\x2F\x2A,已经很熟悉了,其实就是/*
那我们的脚本写什么呢?
新建个文本吧:
这里只写一句话,然后把它以二进制形式添加到图片文件后面。
with open('test.txt', 'rb') as f:
script = f.read()
with open(fname, 'a+b') as f:
f.write(b'\x2A\x2F\x3D\x31\x3B')
f.write(script)
写入脚本之前添加了什么东西呢?
当然是要把前面的“ /* ”闭合掉了,这样中间的数据才能被注释。后面的=1;呢?
别忘了,我们前面还有个BM呀。如果忽视掉注释,最后就剩下了:
bm=1;
alert("You are hacked!!!");
=1;只是为了让语法没错误,能够成功执行到alert语句。
( js语言是弱类型语言, 变量无需声明即可直接使用, 默认是作为全局变量使用的。)
完整代码:
fname = '1.bmp'
#read data of the picture
with open(fname, 'r+b') as f:
buff = f.read()
#replace * and \ in buff
buff = buff.replace(b'\x2A', b'\x00').replace(b'\x2F', b'\x00')
#overide the picture and add /* in the end of it
with open(fname, 'w+b') as f:
f.write(buff)
f.seek(2, 0)
f.write(b'\x2F\x2A')
with open('test.txt', 'rb') as f:
script = f.read()
with open(fname, 'a+b') as f:
f.write(b'\xFF\x2A\x2F\x3D\x31\x3B')
f.write(script)
代码上没有任何难度,只是逻辑上理清即可。
这张图片算是处理完了。那来试试?
简单写个界面吧。
看下图片能否显示出来,再看下脚本能否运行。
浏览器看下:
图片能正常显示,脚本也能正常运行。
很完美的伪装啊。
这里可能有个疑问了,src还能指定二进制文件啊!搞不懂。
https://www.cnblogs.com/bibiafa/p/9366505.html
这里引用下,无论你指定的是什么,我都是按照文本形式读的。(也就是把那些十六进制数字转成对应的ASCII码字符。)
举个简单的例子,我们用txt打开个二进制文件,会出现什么景象?乱码。
你文件的存储方式是二进制,而txt的读取方式是文本,用文本方式读取出来,当然就乱了。有些图片jpg格式换成别的格式就不能显示了,也是这种原因,存储方式和读取方式不匹配。
src给script指定的文件,无论是什么,他都会把它当成文本插入到script标签之间。
别忘了,我们的脚本是转成二进制之后存进去的,而不是直接放进去。script往外读的时候,那些像素点肯定是不知道啥意思,不过没关系,反正我们也注释了。但是我们后面的脚本读出来就不一样了呀。
前面我们已经展示了,读出来是什么。
而我们插入的时候恰恰反过来,根据文本转成二进制插回去,现在你做的就是再读回来而已。
应该解释清楚了吧。
换言之,我们的页面等同于:
<img src="1.bmp" />
<script>
BM/* xxxxx */=1;
alert("You are hacked!!!")
</script>
只是我们没有显示的写在网页上,而是隐藏在了图片里。
嗯,这就是全部的内容了。
这篇文章的意义在于说明图片攻击方式,获得知识的同时,也培养一种安全意识。另一方面,我们博客的服务器还是相当给力的哈。有问题的图片不让你上传。点个赞。
(记:文章中许多点,有些我不会解释,有些解释不到位。不妥处评论指出,我会把您的评论帖进来。)