PHP SECURITY CALENDAR 2017 學習總結-更新中

     這篇文章主要以審計代碼爲主來分析每道題目中所存在的漏洞點,記錄一下本身的學習:javascript

1.Day 1 - Wish Listphp

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'],
                self::UPLOAD_DIRECTORY . $this->file['name']
            );
        }
    }
}

$challenge = new Challenge($_FILES['solution']);

這道題主要是php的弱類型繞過致使的文件上傳漏洞,inarray在比較的時候,好比「5aaaa」==「5」的,由於php會自動將後面的截掉html

這部分有道練習題,漏洞點在:java

select * from user where id=$id;mysql

即在$id處能夠進行報錯注入,updatexml()型注入,或者使用extractvalue()型注入。git

updatexml函數是從特殊字符、字母后面開始截取的,咱們就須要在咱們想要的數據前面拼接上特殊字符。github

extractvalue() :對XML文檔進行查詢的函數
語法:extractvalue(目標xml文檔,xml路徑)

updatexml()函數與extractvalue()相似,是更新xml文檔的函數。
語法updatexml(目標xml文檔,xml路徑,更新的內容)

咱們要使它報錯的點在xml路徑處,默認/xxx/xx是正確的路徑,常見payload以下:正則表達式

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘~’,(select database()))))
select username from security.user where id=1 and (updatexml(‘anything’,concat(‘~’,(select database())),’anything’))

其中concat爲鏈接字符串的函數,頁面會爲咱們返回錯誤,其中錯誤的信息中就包含了咱們想要查詢的敏感數據。sql

若是concat用不了,能夠考慮其餘的字符串鏈接函數,好比make_set()函數,常見payload以下:shell

select updatexml(1,make_set(3,'~',(select user())),1);  //3對應011,那麼就會將第一位與第二位拼接

咱們還能夠找到相似的函數:lpad()、reverse()、repeat()、export_set()(lpad()、reverse()、repeat()這三個函數使用的前提是所查詢的值中,必須至少含有一個特殊字符,不然會漏掉一些數據)。

有一點須要注意,extractvalue()能查詢字符串的最大長度爲32,就是說若是咱們想要的結果超過32,就須要用substring()函數截取,一次查看32位

這裏查詢前5位示意:

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘#’,substring(hex((select database())),1,5))))

https://www.jb51.net/article/87120.htm  這個鏈接是mysql的字符串函數參考

2.Day 2 - Twig

// composer require "twig/twig"
require 'vendor/autoload.php';

class Template {
    private $twig;

    public function __construct() {
        $indexTemplate = '<img ' .
            'src="https://loremflickr.com/320/240">' .
            '<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();

twig是php的模板語言,就像jinjia2是flask的模板語言,都是起到了渲染的做用,在這段代碼中用戶能夠控制的是$nextSlide變量,而後通過filter_var函數進行一個過濾,驗證其是否是正確的url,而後再將過濾後的url通過escape過濾後讓{{}}來渲染,那麼確定要了解這兩個過濾函數是否是可以繞過?看起來它的確只是驗證是不是以://分割的字符串

接下來看twig的escape函數:

twig的中文官方文檔上說:

Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function for the HTML escaping strategy.

escape的過濾策略和htmlspecialchars的過濾規則同樣,就是和htmlspecialchars($link, ENT_QUOTES, 'UTF-8')同樣,設置了ENT_QUOTES之後,就會將單引號和雙引號進行編碼。

因此首先利用javascript://comment來繞過filter的過濾,而後由於//是javascript的註釋符號,因此真正的payload能夠放在下一行,也就是可使用%0a或者%250a來構造一個\n換行符,

因此完整的payload能夠爲:

javascript://comment%0aalert(1),就會觸發xss漏洞。

PHP7.0.25 繞過filter_var可使用0://   

file_get_contents()結合data協議進行xss 

相關連接 https://www.anquanke.com/post/id/101058

 day3-snow flake

function __autoload($className) {
    include $className;
}

$controllerName = $_GET['c'];
$data = $_GET['d'];

if (class_exists($controllerName)) {
    $controller = new $controllerName($data);
    $controller->render();
} else {
    echo 'There is no page with this name';
}

class HomeController {
    private $data;

    public function __construct($data) {
        $this->data = $data;
    }

    public function render() {
        if ($this->data['new']) {
            echo 'controller rendering new response';
        } else {
            echo 'controller rendering old response';
        }
    }
}

漏洞點在class_exist()函數判斷類存不存在的時候會自動調用__autoload函數,若是__autoload函數有讀寫文件或能夠包含文件,那麼就有可能產生危險,在php小於5.4中

都有效,php的目前版本是7.3.2。

這道題又讓我想到了file_exist()函數檢查文件存在時會進行反序列化,仍是不可以徹底信任用戶的數據,須要對數據進行消毒。

Day 4 - False Beard

  

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><user="%s"/><pass="%s"/></xml>';
            $xml = sprintf($format, $user, $pass);
            $xmlElement = new SimpleXMLElement($xml);
            // Perform the actual login.
            $this->login($xmlElement);
        }
    }
}

new Login($_POST['username'], $_POST['password']);

這道題主要是strpos的trick,0==false,那麼出如今字符串第一位也會返回爲0,那麼就能繞過。

Day 5 - Postcard

  

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@vsplate.com';
        } else {
            $data['to'] = $this->sanitize($data['to']);
        }

        if (!isset($data['from'])) {
            $data['from'] = 'none@vsplate.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)

在這個題目中主要學會了escapeshellcmd和escapeshellarg同時使用時會出發漏洞

escapeshellcmd
escapeshellcmd() 對字符串中可能會欺騙 shell 命令執行任意命令的字符進行轉義。 此函數保證用戶輸入的數據在傳送到 exec() 或 system() 函數,或者 執行操做符 以前進行轉義。

反斜線(\)會在如下字符以前插入: &#;`|*?~<>^()[]{}$\, \x0A 和 \xFF。 ' 和 " 僅在不配對兒的時候被轉義。 在 Windows 平臺上,全部這些字符以及 % 和 ! 字符都會被空格代替。
escapeshellarg
escapeshellarg() 將給字符串增長一個單引號而且能引用或者轉碼任何已經存在的單引號,這樣以確保可以直接將一個字符串傳入 shell 函數,而且仍是確保安全的。對於用戶輸入的部分參數就應該使用這個函數。shell 函數包含 exec(), system() 執行運算符 。

  從上面的兩次過濾中能夠看到能夠成功執行curl,對應的題目漏洞的CVE是https://github.com/opsxcq/exploit-CVE-2016-10033,主要利用了mail函數沒有對第五個參數進行嚴格檢查,致使能夠附加惡意參數,而且要求php沒有安裝PCRE,沒有開始safe_mode,這樣對mail的address就會進行鬆散的檢查。

 

Day 6 - Frost Pattern

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函數的正則匹配規則熟悉不熟悉,這裏存在unlink函數,其中$file變量是通過正則匹配替換之後的,即$token變量,這段正則匹配的是除a-z和.-_之間的全部字符,那麼表達的是一個區間,而咱們想防止目錄遍歷的話這裏

須要過濾.(點號),即須要對-進行轉義。

Day 7 - Bells

function getUser($id) {
    global $config, $db;
    if (!is_resource($db)) {
        $db = new MySQLi(
            $config['dbhost'],
            $config['dbuser'],
            $config['dbpass'],
            $config['dbname']
        );
    }
    $sql = "SELECT username FROM users WHERE id = ?";
    $stmt = $db->prepare($sql);
    $stmt->bind_param('i', $id);
    $stmt->bind_result($name);
    $stmt->execute();
    $stmt->fetch();
    return $name;
}

$var = parse_url($_SERVER['HTTP_REFERER']);
parse_str($var['query']);
$currentUser = getUser($id);
echo '<h1>'.htmlspecialchars($currentUser).'</h1>';
parse_str

功能 :parse_str的做用就是解析字符串而且註冊成變量,它在註冊變量以前不會驗證當前變量是否存在,因此會直接覆蓋掉當前做用域中原有的變量。

定義 :void parse_str( string $encoded_string [, array &$result ] )

若是 encoded_string 是 URL 傳入的查詢字符串(query string),則將它解析爲變量並設置到當前做用域(若是提供了 result 則會設置到該數組裏 )。

 由於parse_str不會檢查要註冊的變量是否已經存在,因此會直接對變量進行覆蓋,這裏能夠覆蓋config變量從而鏈接任何咱們想要鏈接的數據庫及數據表。

Day 8 - Candle

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";
}

這段代碼裏面咱們能夠提供正則表達式,和須要進行匹配的字符串,其中須要知道正則中的自匹配,\1就是匹配到的結果,preg_replace中的/e模式會將匹配到的字符串看成命令執行函數的參數進行執行,因此${phpinfo()}能夠由正則匹配到,從而進行執行,

\e是在替換完字符串之後再用eval函數檢測替換後的字符串。

php中在用${}包裹變量時,其中會把花括號中的字符串首先看成變量名進行解析,在解析的過程當中若是該括號包含的是函數,則會首先對函數進行執行,若是是變量則會首先取變量的值,而後結合外面的${}生成新的變量,以值做爲鍵名,也就是php裏面的

動態變量。

 

class LanguageManager
{
    public function loadLanguage()
    {
        $lang = $this->getBrowserLanguage();
        $sanitizedLang = $this->sanitizeLanguage($lang);
        require_once("/lang/$sanitizedLang");
    }

    private function getBrowserLanguage()
    {
        $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
        return $lang;
    }

    private function sanitizeLanguage($language)
    {
        return str_replace('../', '', $language);
    }
}

(new LanguageManager())->loadLanguage();

 這裏爲了防止目錄遍歷漏洞使用str_replace()函數,可是....//可以繞過,若是是str_replace(array('../','./'),"",$str),那麼.....///就能繞過

相關文章
相關標籤/搜索