0x00 漏洞描述
? 在實際開發(fā)過程中文件上傳的功能時十分常見的,比如博客系統(tǒng)用戶需要文件上傳功能來上傳自己的頭像,寫博客時需要上傳圖片來豐富自己的文章,購物系統(tǒng)在識圖搜索時也需要上傳圖片等,文件上傳功能固然重要,但是如果在實現(xiàn)相應功能時沒有注意安全保護措施,造成的損失可能十分巨大,為了學習和研究文件上傳功能的安全實現(xiàn)方法,我將在下文分析一些常見的文件上傳安全措施和一些繞過方法。
? 我按照最常見的上傳功能–上傳圖片來分析這個漏洞。為了使漏洞的危害性呈現(xiàn)的清晰明了,我將漏洞防御措施劃分為幾個不同的等級來作比較
0x01 前端HTML頁面代碼
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>
file_upload_test
</title>
<body>
<form enctype="multipart/form-data" action="upload_1.php" method="POST" />
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
選擇你要上傳的圖片:
<br />
<input name="uploaded" type="file" /><br />
<br />
<input type="submit" name="Upload" value="上傳" />
</form>
</body>
</html>
前端的實現(xiàn)代碼均為以上。界面如下圖:
0x01 零防御的PHP上傳代碼
源代碼 upload_0.php
<?php
if (isset($_POST['Upload'])) {
$target_path = "uploads/";
$target_path = $target_path . basename( $_FILES['uploaded']['name']);
if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $target_path)) {
echo '<pre>';
echo '您的圖片上傳失敗.';
echo '</pre>';
} else {
echo '<pre>';
echo $target_path . '文件已經(jīng)成功上傳!';
echo '</pre>';
}
}
?>
這段PHP代碼對上傳的文件沒有任何的過濾,只是將上傳的文件直接存儲到了網(wǎng)站uploads文件夾下,此時如果我們上傳一個一句話木馬并通過瀏覽器訪問加上參數(shù)的地址或者使用中國菜刀直接連接,就可以為所欲為了。
//一句話木馬 <?php eval($_GET['cmd']);?>
0x01 初級防護-驗證文件類型
源代碼 upload_1.php
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
$target_path = "uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
//識別文件類型
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
echo "<pre>圖片上傳失敗</pre>";
}
else {
echo "<pre>{$target_path} 圖片上傳成功!</pre>";
}
}
else {
echo "<pre>只允許上傳jpg或者png格式的圖片文件,且文件大小不能超過100k</pre>";
}
}
?>
防御方法
初級防御的代碼在審查用戶上傳的文件時加入了“Content-Type”驗證,代碼會自動識別文件類型并將文件類型以表單的形式進行驗證,如果“Content-Type”是image/jpeg或者image/png時文件可以上傳 成功,算是初級防御。
繞過方法
用BurpSuite截斷代理修改數(shù)據(jù)包的相關(guān)字段即可完成繞過,本例上傳的文件時shell.php,代碼會將此文件的Content-Type識別為application/x-php,直接將application/x-php改為mage/jpeg即可繞過驗證,而且對于文件大小的限制也是可以直接修改”MAX_FILE_SIZE”的方式突破限制從而上傳更大的文件。
?文章來源地址http://www.zghlxwxcb.cn/news/detail-786399.html
修改前
修改后
?
?
0x02 一般防護-驗證文件后綴
源代碼 upoad_2.php
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
$target_path = "uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
//記錄文件信息
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
//識別文件后綴
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
echo "<pre>圖片上傳識別.</pre>";
}
else {
echo "<pre>{$target_path} 圖片上傳成功!</pre>";
}
}
else {
echo "<pre>只能上傳格式為jpg和png的圖片.</pre>";
}
}
?>
相比較于前一種比價簡單的驗證content-type的防護方式,一般級別的防護措施換成了驗證文件后綴的方式,順便多說一句,在為了安全性設置一些限制時,使用白名單永遠比設置黑名單要安全的多,因為總會有=各種方式繞過黑名單的方式或者是一些針對不同服務器系統(tǒng)或著服務器的特殊解析原理而造成的一些安全隱患。以下是獲取文件后綴的代碼:
?
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1)
通過本語句獲取文件名中最后一個“.”后的字符識別上傳的文件名的后綴,并將后綴存儲在一個變量中。
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) )
而在if的邏輯判斷中,需要上一條語句截取到的文件后綴為“jpg”,“jpeg”或者“png”,切且上傳的文件大小不得大于10000b,如果只有這個限制方法的話,可以直接使用burpsuite進行00截斷,從而使得在文件后綴驗證時通過但是在文件轉(zhuǎn)儲的時候忽略掉00之后的內(nèi)容從而實現(xiàn)后綴欺騙,具體方式如下:
假設網(wǎng)站只能上傳圖片文件并在后臺歐了后綴的限制
此時你要上傳一個shell.php的一句話木馬
將”shell.php”改為”shell.php 1.png”
使用burpsuite截斷代理,攔截數(shù)據(jù)包
將”shell.php 1.png”發(fā)送至decoder模塊,從text模式轉(zhuǎn)換為hex編輯模式,找到”shell.php 1.png”中空格對應的hex值“20”,將20改為00
從hex模式恢復為text并將修改過的字符串替換原來報文中的”shell.php 1.png”
發(fā)送報文,操作成功后會顯示文件上傳成功
操作成功后會顯示文件上傳成功,在php版本小于5.3.4的版本中,當Magic_quote_gpc選項為off時,可以在文件名中使用%00截斷,所以可以把上傳文件命名a1.php%00.png進行繞過,我們用bp抓包檢測一下文件類型。 可以發(fā)現(xiàn)文件類型是png成功繞過前端,并且到服務器文件會被解析成php文件,因為00后面的被截斷了,服務器不解析。
但是在本例中,00截斷的方法不再有效,因為if條件中還有一個getimagesize()函數(shù),此函數(shù)會自動識別上傳的圖片的文件頭,長寬,mime類型等信息,因此如果上傳的文件不是圖片將無法上傳。繞過這個限制的方法是制作圖片馬,我是在win環(huán)境下制作的,只需準備一個圖片大小較小的jpg或者png格式的圖片,打開cmd使用命令:
copy 1.jpg/b+shell.php 2.jpg
1
來合成一張圖片馬,如果用二進制編輯器打開此文件會發(fā)現(xiàn)一句話木馬寫到了文件的后面,把這樣的文件上傳時,由于文件頭仍然是jpg的文件頭,getimagesize()函數(shù)也會正確的返回圖片的大小和文類型,因此通這種方式可以繞過getimagesize()函數(shù)的限制,再結(jié)合00截斷即可上傳木馬并在服務器端將文件解析為php腳本,從而正確執(zhí)行。
但是如果服務器的PHP版本較高,則無法通過此方法進行漏洞的利用,需要結(jié)合文件包含漏洞進行利用。
0x03 無解的防護-全方面限制
當然安全只是相對的,沒有絕對的安全,一下代碼對輸入的文件進行了多種方式的審查并進行了重新編碼,是目前比較完善了安全防御措施。
源代碼 upload_2.php
?
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// 檢查token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
$target_path = 'uploads/';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
//判斷是否是一張圖片
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&getimagesize( $uploaded_tmp ) ) {
//重新制作一張圖片,抹去任何可能有危害的數(shù)據(jù)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );
//文件轉(zhuǎn)儲
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
$html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
$html .= '<pre>Your image was not uploaded.</pre>';
}
//刪除所有暫時文件
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
//無效文件
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// 添加抗csrf驗證
generateSessionToken();
?>
添加了sessionToken,驗證會話身份,用于防止csrf攻擊
使用md5( uniqid() . $uploaded_name )函數(shù),uniqid()函數(shù)是根據(jù)當前的時間,生成一個唯一的id,跟大多數(shù)隨機函數(shù)一樣,基于時間的隨機函數(shù)在一定條件下也是可以差生碰撞的,因此本例中采用了md5()函數(shù)來保證生成id的唯一性,而且由于md5()函數(shù)對上傳的文件名進行了重命名,因此無法使用00截斷的方式來上傳php或者其他惡意腳本文件。
以白名單的方式限制上傳的文件后綴
限定上傳的文件大小不得超過10000
通過imagecreatefromjpeg()和imagecreatefrompng()函數(shù)將上傳的圖片文件重新寫入到一個新的圖片文件中,這兩個函數(shù)會自動將圖片中的有害元數(shù)據(jù)抹除,因此即使黑客上傳了一張圖片馬也會被這個函數(shù)過濾成一個純正的圖片。
imagedestroy( $img )將用戶上傳的源文件刪除
unlink( $temp_file )刪除過濾過程中產(chǎn)生的任何臨時文件
0x04 個人總結(jié)
web漏洞種類繁多,利用方法奇葩而有趣,值得研究和學習
學習路線
對于從來沒有接觸過網(wǎng)絡安全的同學,我們幫你準備了詳細的學習成長路線圖??梢哉f是最科學最系統(tǒng)的學習路線,大家跟著這個大的方向?qū)W習準沒問題。
同時每個成長路線對應的板塊都有配套的視頻提供:
?文章來源:http://www.zghlxwxcb.cn/news/detail-786399.html
?
到了這里,關(guān)于PHP文件上傳漏洞原理以及防御姿勢的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!