文章目录
文件上传本身是指一个功能,比如用户可以上传头像、上传资料等。而如果能够上传任意文件,则可能存在上传漏洞(注意是可能,不是一定)。
任意文件上传漏洞的产生需要满足三个条件:
1、文件能上传;
2、文件能被服务器解析(你传的文件要能被当做后端脚本处理);
3、能访问得到上传的文件,如果访问不到,则无法对文件进行控制,也就没有漏洞的说法。(文件路径通常只有在根目录里面才能访问到)
任意文件上传漏洞,首先要有能上传文件的的地方,即要有文件上传点。有一些网站虽然貌似没有上传点,但是其实存在上传代码的。虽然没有明确使用上传功能 ,但使用了上传有关的函数,这种情况需要通过代码审计去看。
文件上传一般是要先登陆目标网站,用管理员账号或者普通账号都可以,最常见的就是上传头像。
一般目标网站都会有一些防护。现在我们来介绍一下网站常见的防护方法,知道了网站是如何防护该漏洞的,有利于我们更好地开展渗透测试。
对文件上传的防护大体上可以分为两类,一类是客户端检测,另一类是服务端检测。
客户端检测是指依靠浏览器,用JS代码去检测。一般是在网页上写一段Js脚本,用Js去检测,在文件未上传时,校验文件的后缀名,检测的方式有白名单和黑名单两种。
那么如何判断是否为客户端检测?
在浏览加载文件,但还未点击上传按钮时便弹出对话框,内容如:只允许上传.jpg/.jpeg/.png后缀名的文件,而此时并没有发送数据包,所以可以通过抓包来判断,如果弹出不准上传,但是没有抓到数据包,那么就是采用了前端验证。
从安全的角度来看,前端验证非常不可靠,传正常文件再改数据包就可以绕过,甚至关闭JS都可以尝试绕过。很多程序员使用JavaScript来拒绝非法文件的上传,这种验证方式对一些普通用户防止上传错误还可以,但是对专业的技术人员来硕,这是非常低级的验证方式。攻击者可以用非常多的方法来突破客户端的验证。
由于前端代码对用户完全开放,因此很容易通过分析前端代码来绕过,所以相当于没有检测。可以用以下三种方式去绕过或者删除前端的检测:
1、用burp抓个返回包然后把前端代码里面文件检测的部分代码删掉(这种方式也叫中间人攻击);
2、点检查,然后找到文件检测部分的JS代码,删掉即可,(但是最保险的办法还是抓包后再去删);
3、根据前端代码允许上传的类型,预先把木马文件后缀改成相应的后缀,比如jpg,通过前端代码的检测以后,会自动往服务器发包,然后通过burp去拦截这个包,把后缀名改回来。
服务端检测是先将文件上传到服务器,然后服务器依靠后端代码去检测上传的文件是否合规。服务器脚本一般会检测文件的MIME类型、扩展名是否合法,甚至可能会去检测文件中是否嵌入恶意代码。
服务端检测几个常见的手段有:检查Content-Type(内容类型)、检查后缀(检查后缀是主流,根据后缀来决定用什么方式来处理这个文件)、检查文件头等。
黑名单:不允许某某类型的文件上传;
白名单:只允许某某类型的文件上传。
显然白名单的方式比黑名单更安全,黑名单很容易就会被绕过。
那么如何判断目标是黑名单还是白名单呢,其实很简单,只要上传一个莫名其妙的后缀,比如1.czczxca,这个后缀的文件肯定不存在,所以如果能上传成功,说明目标是黑名单检测,如果上传失败,则目标是白名单检测。
假如后端代码采用黑名单检测,那么开发人员往往会预先设置一些后缀名作为黑名单,不允许某些后缀名的文件上传,那么此时就需要去尝试各种不同的绕过方法了。
假设我们要上传的文件为php文件,那么就要去尝试,看看什么样的后缀名会被当成php文件解析。实际上,除了后缀为.php的文件会被当成php文件解析, 还有phtml php3 php4 php5之类的后缀也会,可以都去尝试一下,看看能否上传成功。
对于不同的文件,可以采用不同的后缀去测试,虽然概率很小,但万一成功了呢?
假如开发者把后端代码写的很严格,甚至后缀名的大小写也写进了黑名单,那么就可以尝试用.htaccess文件来绕过了。
.htaccess全称是Hypertext Access(超文本入口)。htaccess文件也被称为分布式配置文件,是Apache特有的的针对目录改变配置的方法。通过在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。该文件可以针对不同的目录采用不同的策略。
一般后端的黑名单不会过滤把.htaccess后缀写进去,那么我们可以先上传一个.htaccess文件上去,提前设置好让它把.jpg文件当做php文件来解析,这样后续只需要上传.jpg文件就可以了。
.htaccess文件的内容如下:
AddType application/x-httpd-php .jpg
这个指令代表着.jpg文件会当做php文件来解析。
很多windows操作系统是禁止将文件名设置为空的,但是我们可以用cmd命令来实现。首先新建一个名为1.txt的文件,然后输入上述代码。接着在该文件夹下打开cmd窗口,输入:
ren 1.txt .htaccess
对于那种老版本的WEB容器,是区分大小写的,所以这种方法只对WEB容器非常古老的版本有效。原理很简单,在解析的时候,windows的文件和文件夹都是不区分大小写的,而WEB容器区分大小写,因此上传的时候就有可能绕过该WEB容器,从而实现绕过后缀名检测。
在文件名后面留一个空格,然后上传上去后空格会被自动的省略,但是看源码可知道,源码中黑名单中没有过滤空值,那么php和php空格,当然是不一样的。
注意:windows会自动去除文件末尾的空格,所以需要先上传正常的文件,然后抓包在后缀名的地方加空格,再放包,就可以了。这里的空格改成点也是一样的。
这种情况下需要对目标检测方式的源代码有一定的了解,要么了解目标网站的开发风格,要么知道源代码是如何对后缀名进行处理的,从而根据处理规则,巧妙绕过后缀名的检测。来看下面一段代码:
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']); //$_FILES['upload_file']['name'] //获取上传的文件名,比如 1.jpg
$file_name =deldot($file_name);//删除文件名末尾的点 1.jpg
$file_ext = strrchr($file_name, '.'); //查找指定字符在字符串中的最后一次出现,并返回从该位置到字符串结尾的所有字符,这里实际上就是取后缀名
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}
根据以上代码,假如输入的文件后缀名为1.php.空格.,则处理过后会剩下1.php. 在windows中,1.php.会自动变为1.php,从而实现了绕过后缀名的检测。
来看一段源代码:
$file_name = str_ireplace($deny_ext,"", $file_name);
这里的意思就是把检测到的危险字符替换为空,php被替换为空,那么假设上传的文件后缀名为pphphp,被替换为空后就会变为php,从而实现了绕过。
windows下硬盘格式主要有FAT16、FAT32、NTFS等。ADS(NTFS交换数据流)是NTFS磁盘格式的一个特性,在NTFS文件系统下,每个文件都可以存在多个数据流。通俗的理解,就是其它文件可以“寄宿”在某个文件身上,而在资源管理器中却只能看到宿主文件,找不到寄宿文件。
我们先新建一个1.txt,里面没有内容,然后在当前文件夹打开cmd,输入:
echo 123>1.txt
这表示将123写入1.txt
继续输入:
echo 123>1.txt:a.txt
这样在1.txt中没有123,并且该txt文件的大小没有变化。
那么如何访问刚才写入的文件呢?
输入:
notepad 1.txt:a.txt
就可以看见刚才输入的123
如果要显示这个文件,可以在cmd命令里输入:
dir /r
这种方式可以用来藏资料。(但是还不是最好的方法)
注意,实际上在windows中,
a.txt::$DATA就是默认不修改文件流的情况,相当于a.txt。对于windows系统而言,这两种写法没什么区别。但是对于后端的检测而言,这两种写法完全不一样。所以生成一个正常的a.txt,利用windows特性,可在后缀名中加::$DATA绕过。注意,上传成功以后,要访问时,就直接访问该文件,不要再加::$DATA
注意:只有windows可以这么操作。
了解%00之前我们要先了解0x00。
00截断:类似我们用对讲机或者电报通话时,说完一句话会用over表示一句话说完了,而电脑也需要一个截断的信号,而这个信号就是00截断。
0x是一个十六进制表示方式,声明这是十六进制数,00实际上就是表示ascii码值为0,有些函数在处理这个字符的时候会把这个字符当做结束符,他们就读取到这里认为这一段结束了。
了解这个有什么用呢?
在文件上传时,如果遇到了白名单机制只允许上传jpg后缀的,我们该怎么办?JPG格式并不会被解析,那么我们需要绕过上传过滤。
假如我写了1.php%00.jpg 传参之后,有些过滤都是直接匹配字符串,他强行匹配到了结尾是.jpg,然后允许上传,但是php的函数去执行的时候他读取到0x00认为结束了,那么这个文件就变成了1.php,从而实现绕过白名单。
%00实际上和00截断是一模一样的原理,只不过%00是经过URL编码的,%00解码后就是0x00截断的那个字符。
需要注意%00和00的区别,只有GET传参会进行一次URL编码,其他的传参方式不会,所以只有GET传参能够识别这种%00的截断。
对于只允许指定Content-Type(内容类型)这种防护手段,最简单的绕过方法是直接上传一个允许的文件类型再抓包修改后缀名。以上传jpg文件为例,先上传该文件,抓包修改后缀名为实际的后缀名.php,由于上传的时候是jpg,所以浏览器发送的数据包中,Content-Type的类型默认为jpg,那么此时发送的文件就可以绕过后端的检测。
实际上不一定非得把上传php文件,我们可以把木马藏在图片中,直接上传到目标服务器。假如目标存在文件解析漏洞,并且能够被我们访问到,这样木马就可以生效了。
常用的图片木马是图+马+图的形式。因为很多网站会检测头部是否是JPG 尾部是否是JPG 检测渲染是否有问题。我们就可以把一句话木马藏在中间。这种马相对而言比较难被发现。
例如,我们可以先保存一张小图片,命名为1.jpg,然后将一句话木马写到2.txt里,接着打开命令行,输入:
copy 1.jpg/b + 2.txt 3.jpg
这条命令可以把这两个文件合并,合并后显示为3.jpg。(有jpg的话需要加个/b)
假如目标对用户上传的图片进行二次渲染,比如给图片重新截图、加水印之类的。最简单的绕过方法是直接上传gif文件去绕过(前提是目标允许上传gif格式的图片)。就是先把正常的gif图片上传上去,再下载下来,然后用文本编辑器打开,在HEX编码的页面查看都有哪些乱码变化了,一般在第四行开始可以加入一句话木马。
条件竞争是一个需要拼运气的方法,我建议除非实在是无计可施了,不然不要采用这种方式,因此频繁访问目标网站,ip容易被ban,并且也容易引起网站管理员的注意,毕竟渗透测试讲究润物细无声,条件竞争动静有点大了,属于比较冒险的做法。接下来我讲解一下条件竞争产生的原理及如何使用条件竞争。
竞争条件发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,从而忽视服务器并发执行多个线程可能带来的冲突问题,这就会导致一些意想不到的结果。
线程同步机制确保两个及以上的并发进程或线程不同时执行某些特定的程序段,该程序段也被称为临界区(critical section)。如果没有处理好临界区则可能发生“竞争条件”问题。
简单理解,两只哈士奇(线程)同时去抢一个丢出去的飞盘(资源),不知道到底哪只能抢到,此处便形成了竞争。
一般而言我们上传文件,上传了但是最后却因为过滤或者因为其他原因被删除了,那么我们可以使用条件竞争来尝试上传。我们实际上是和unlink,是和删除文件的函数进行竞争。假如我不断的上传发包,然后我同时也不断的访问那个我们上传上去的文件的地址,我们就开始和服务器的函数比手速了,函数执行都是要时间的,检测和删除也都需要时间,由于我疯狂上传,服务器来不及执行删除操作,那个时间可能很短,然后被我访问到了,就可以执行PHP命令了,那我不就比服务器手速更快了吗?
上传文件的检测流程大体上分为两种:
先检测:检测合格,然后上传(类似检票进站);
先上传:上传后再检测,把不合格的删除(类似坐公交车,先上车后买票);
检测文件是需要时间的,在检测的空档期,如果能访问到该PHP代码,就能够执行PHP指令。
所以条件竞争的核心思路是:疯狂上传php文件,然后疯狂访问这个文件,由这个php去生成另外一个文件。由于检测代码只会删除你上传的文件,并不会删除你生成的文件,所以只要生成文件成功,那么就算上传的文件被删了,也没关系,因为我们的目的已经达到了。
注意:在使用条件竞争之前,我们需要知道文件被上传到哪里去了,否则这种方法是不能用的。
代码举例:
<?php $a = '<?php @eval($_REQUEST[\'a\'])?>';
file_put_contents('1.php',$a); ?>
注意:这里面的代码要写单引号,否则会被当成代码解析,而我们要的是把字符串写入文件中。
本文详细讲解了文件上传漏洞的概念、常见的防护方法、及绕过客户端和服务端检测的方法,后面我会再写一篇文章介绍如何利用文件解析漏洞让目标服务器解析我们上传的文件,从而实现getshell,敬请期待!
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信