从0到1,从无到有……
开始涉足代码审计,了解了一些敏感函数之后,是时候拿点实例来练练手了!
把握大局(分析网站结构)-> 通读全文法/敏感函数参数回溯法/定向功能分析法 ->
文件上传
move_uploaded_file
用sexy法师的审计系统扫一扫,发现存在四个地方存在文件上传(move_uploaded_file 函数)
1.admin_pic.php
这个只是在图片列表“修改”选项处的重新上传,并将旧照片替换掉,用burp改参数的确可以直接传一个php文件,但是后缀名是不变的,所以没有利用的地方。
2.fun.php
定义函数的汇总 php。这里定义了一个 up_img 函数,规定了大小、格式、是否重名……
同样的,上传这一关很容易就bypass过去了。但是仔细审计 up_img 函数就会发现。它的 $file_name 不是直接就从上传那直接获得,而是经过一系列的拼接,估计就是为了防止稍微用burp改一下参数就导致文件上传漏洞的产生吧!
3.img.class.php
这里也有一些关于图片信息的函数,文件名是由“年月日时分秒+id(rand)+拓展名(ext)”拼接而成
4.upload_more.php
上传多张图片
在upload.php中测试怎么bypass
经过上述php的审计,得出他是用白名单策略来检查扩展名,所以就试了下 nginx空子节漏洞,用burp在.jpg后加上%00.php后发现上传的jpg文件已经被解析为php了。所以…. :)
难道只存在解析漏洞?再找找看~~
但是我又在之前已嵌入php代码的png文件中尝试用%00.php 来执行png中的代码,发现不得行:
初步分析是过滤了%,
但是直接在url上加%后enter,发现,没有被转义啊!
But Why?
getimagesize()
验证文件头只要为GIF89a,就会返回真
1.admin_pic.php
if($is_thumb){//开启缩略图
$file_info=@getimagesize($file_name);
if(empty($file_info)){msg('图片不存在,操作失败');}
2.fun.php
if(!move_uploaded_file($file['tmp_name'],$file_name)){
msg('图片上传失败','',0);
}
$file_info=@getimagesize($file_name);
switch($file_info[2]){
case 1:
$php_file=@imagecreatefromgif($file_name);
break;
case 2:
$php_file=@imagecreatefromjpeg($file_name);
break;
case 3:
$php_file=@imagecreatefrompng($file_name);
break;
}
上传图片下面有getimagesize()函数,对参数进行处理。
if($_sys['image_type'][0]){
$logo=CMS_PATH.'upload/'.$_sys['pic'];
$logo_info=@getimagesize($logo);
switch($logo_info[2]){
case 1:
$logo_file=@imagecreatefromgif($logo);
break;
case 2:
$logo_file=@imagecreatefromjpeg($logo);
break;
case 3:
$logo_file=@imagecreatefrompng($logo);
break;
}
虽然加了png文件头,识别为图片后缀。但是想绕过上传,怕是不大行,因为都是用同一套上传验证机制。这个函数好像在这里好像只是用于读取长宽,并判断后缀名,下面也对postion位置进行case分类[1-2-3],然后拼接。用imagecopy拷贝一下图像参数。
imagecopy($logo_img,$logo_file,$position[0],$position[1],0,0,$logo_info[0],$logo_info[1]);
文件包含
遍历了所有包含include的文件,全都是直接拼接的路径like “ include(DATA_PATH.$lang.'_info.php'); ",没有发现包含来自($_GET or $POST )来的文件,所以。。在当前的知识框架下,我好像找不到其他利用的方式,也就无法配合伪协议+文件上传漏洞来利用。
文件操作
任意文件读取
先从 file_get_contents() 入手
1 -> karma@d0g3.cn.php
$privKeyStr = file_get_contents($this->DKIM_private);
DKIM是域名密钥识别邮件标准 ,所以此处应该只是将隐私信息赋给 $privkeystr ,没有发现可控的地方。
```Php
$file = tempnam('', 'karma@d0g3.cn');file_put_contents($file, $body); //TODO check this worked
$signed = tempnam("", "signed");
if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)) {@unlink($file);
@unlink($signed);
$body = file_get_contents($signed);
```$signed = tempnam("", "signed");
// tempnam(dir,prefix) 创建一个具有唯一文件名的临时文件。
@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)
// openssl_pkcs7_sign —> Sign an S/MIME message 好像是一个类似数字签名验证的东西
$signed
里面的数据是在验证中从$file
中传过去的,而 $file <- sign_cert_file ,好像也没有可控的变量 :($file_buffer = file_get_contents($path);
$file_buffer = $this->EncodeString($file_buffer, $encoding); // base64转义
2 -> admin_db.php
elseif($action=='save_import'){
if(!check_purview('data_import')){msg('<span style="color:red">操作失败,你的权限不足!</span>');}
$fl = $_GET['fl'];
$time = isset($_GET['time'])?$_GET['time']:'';
$rand = isset($_GET['rand'])?$_GET['rand']:'';
$page = isset($_GET['page'])?$_GET['page']:1;
if(empty($fl)){err('<span style="color:red">参数传递错误,请重新操作</span>');}
if($time&&$rand&&$page){
$import_file =DATA_PATH.'backup/'.$fl.'/db_backup_'.$time.'_'.$rand.'_'.$page.'.sql';
if(@file_exists($import_file))
{
$data=@file_get_contents($import_file);
$data=explode(";\n",trim($data));
if(!empty($data)){
foreach($data as $k=>$v){
$GLOBALS['mysql']->query($v);
}
}
show_htm('卷'.$page.'导入完成','?action=save_import&fl='.$fl.'&time='.$time.'&rand='.$rand.'&page='.($page+1).'&nav='.$admin_nav.'&admin_p_nav='.$admin_p_nav);
}
$file_get_contents
<-$import_file
<-(DATA_PATH.'backup/'.$fl.'/db_backup_'.$time.'_'.$rand.'_'.$page.'.sql')
其中
$f1
、$time
、$rand
、$page
都是通过 $_GET 传递的,所以,是可控的,看看是否有利用的价值。三元运算符: ?:
```php
<?php
// Example usage for: Ternary Operator
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];// The above is identical to this if/else statement
if (empty($_POST['action'])) {
$action = 'default';
} else {
$action = $_POST['action'];
}
?>
```
explode() 把字符串打散为数组trim() 移除字符串两侧的字符
3 -> tpl.class.php
if($include_cache){$this->template_is_cache=0;}
$this->template_file=$this->template_dir.$template_file.'.html';
if(!file_exists($this->template_file)){die('不存在模板文件'.$template_file.'.html');}
$this->template_file_content=file_get_contents($this->template_file);
$this->template_com_file=$this->template_compile.$template_file.'_'.$this->template_lang.$this->mb.'_compile.php';//编译文件
$this->cache();//生成缓存文件
$this->compile();//编译模板
$this->view($is_copy);//显示模板
if($this->tp){echo $this->content;}
$this->template_file
<-$this->template_dir.$template_file.'.html'
未发现可控变量,而且模版是直接从数据库中调出,并没有上传模版的地方。
再从 fread() 入手
1 -> fun.php
function copy_lang($lang){
if(isset($lang)){
if(!$fp=@fopen(LANG_PATH.'lang_cn.php','r')){
msg('基本语言包不能操作,请检查是否有操作文件的权限','javascript:history.go(-1);');
}
$fl=fread($fp,filesize(LANG_PATH.'lang_cn.php'));
unset($fp);
$fp2=@fopen(LANG_PATH.'lang_'.$lang.'.php','w');
return fwrite($fp2,$fl);
function creat_inc($fl,$str){
if(file_exists($fl)){@unlink($fl);}
if(!$fp=@fopen($fl,'w')){
msg('文件打开失败,请检查是否有足够的权限操作文件');
}
flock($fp,LOCK_EX);
if(!fwrite($fp,$str)){
msg('写入文件失败,请检查是否有足够的权限操作文件');
}
flock($fp,LOCK_UN);
unset($fp);
生成语言配置文件,好像。。没办法从基本语言包处入手啊!
2 -> admin_template_php
elseif($action=='xg'){
if(!check_purview('tpl_manage')){msg('<span style="color:red">操作失败,你的权限不足!</span>');}
$file = $_GET['file'];
$path=CMS_PATH.$file;
if(!$fp=@fopen($path,'r+')){err('<span style="color:red">模板打开失败,请确定【'.$file.'】模板是否存在</span>');}
flock($fp,LOCK_EX);
$str=@fread($fp,filesize($path));
$str = str_replace("&","&",$str);
$str= str_replace(array("'",'"',"<",">"),array("'",""","<",">"),$str);
flock($fp,LOCK_UN);
fclose($fp);
include('template/admin_template_xg.php');
$path
<- CMS_PATH.$file
<-$_GET['file']
虽然变量可控,但是好像没有echo的地方啊,也无法形成任意文件读取漏洞~ :(
3 -> karma@d0g3.cn.php
多个PHP karma@d0g3.cn函数引发的命令执行漏洞分析
XSS
SQL Injection
1-> member.php
登陆的时候随便测试了一下万能密码,发现 ‘ 被 \' 转义了,= 也被过滤了。
但是在burp上稍微改改参数,但是会显示密码不正确,好像还没看懂如何验证登录。
首页的搜索框是不是也可以看看有没有注入的点?
好像一有query语句,就会有x和y……
然后sql语句执行了有上90条~估计是遍历各个地方看看有没有所需的东西。
代码/命令执行
preg_replace()
popen()
- karma@d0g3.cn.php
if ($this->SingleTo === true) {
foreach ($this->SingleToArray as $key => $val) {
if(!@$karma@d0g3.cn = popen($sendmail, 'w')) {
throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fputs($karma@d0g3.cn, "To: " . $val . "\n");
fputs($karma@d0g3.cn, $header);
fputs($karma@d0g3.cn, $body);
$result = pclose($karma@d0g3.cn);