Logic flaws

owasptop10

A logic flaw occurs when a programmer thinks a user will do always as they expect, and fail to handle scenario in which they don't.

  • The user visit the page "payment" before the page "checkout"
  • The user manually send a form and do not use the HTML form

A more concrete example is that if the developer used an HTML SELECT tag with a list of countries, the developer may not check the submitted value as they expect it to be within their list of countries.


Mass Assignment

api_mass_assignment

In an effort to simplify database interactions, it's common to use ORM (Object-relational mapping) which are objects linked to the database (erasing the SQL code behind).

They are often vulnerable to mass assigment in which we can update fields that we were not "supposed" to.

For instance, if we can change our username and add the field status, we may be able to change our status using this route.

-{"username":"toto"}
+{"username":"toto","status":"admin"}

πŸ‘» See also: Ruby/Rails.


PHP $_REQUEST logic flaw

authenticationbypass

The PHP variable $_REQUEST is sometimes used to access $_GET and $_POST. It's a merge of both, in case of conflict, $_POST values win.

It may be used for convenience, for instance, when chaining a $_POST form to a $_GET form. The problem is that if someone manually add key/values in $_POST, they can erase values in $_GET.

There is a logic flaw if the developer do not check previously checked values, such as the previous form $_GET values, and assume that after checking them once, they are "safe" now.

Illustration 🌺

The fist form is using GET. The developer check the parameter n βœ….

$ curl 'URL/step1?n=5'

In the second form, the user submit a value in a POST form. The developer only check the new value square ❌.

$ curl 'URL/step2?n=5' -d 'square=36'

But, if the user used:

$ curl 'URL/step2?n=5' -d 'square=36&n=6'

Then, in $_REQUEST['n'] there would be 6, an uncheck value.


PHP Loose Comparison And Type Juggling

type_juggling php_type_juggling deserialization_modifying_serialized_data_types

PHP supports loose (==, !=, <>) and strict (===, !===) comparison. When using loose comparison, PHP cast variables to the same type using Type Juggling before comparing their values. In PHP 5.6 (docker) and older, some results are quite surprising:

  • "abc" == 0 is True (<ignored_invalid> == 0)
  • "1a2b3" == 1 is True (1<ignored_invalid> == 1)
  • "12abc3" == 12 is True (12<ignored_invalid> == 12)
  • ...

Additionally, "abc" == True is True for all versions.

πŸͺ¦ PHP Type Juggling is difficult to exploit as usually GET/POST data are strings unless they are unserialized using JSON.

Loose Comparison β€” Exploiting STRCMP

The strcmp function may be also exploited. In PHP 5.6, it returns an integer which indicates the number of different characters.

  • strcmp("abc", "abd") == 0 is False
  • strcmp("abc", "abc") == 0 is True
  • strcmp([], "abd") == 0 is True (Returns NULL, NULL==0 is True)

πŸ“š Refer to HTTP Parameter Pollution

PHP Magic Hashes

php_loose_comparison

Even in PHP 8 and newer, the following equalities are TRUE:

"0e299969168079221277306999992834" == "0"
"0e66507019969427134894567494305185566735" == "0e1337"
"0e66507019969427134894567494305185566735" == "0e4242424242"

There are known plaintext that result in a hash starting with 0e. Refer to spaze/hashes (0.7k ⭐) repository.

  • hash("md2", 'Oq9wqi64') == "0" is True
  • hash("sha1", 'aaroZmOk') == "0e1337" is True
  • ...
// Get A Magic Hash For Hash Type 'fnv164'
while (1) {
    $p = bin2hex(random_bytes(8));
    $h = hash('fnv164', $p);
    if ($h == "0") {
        echo "Found hash($p)=$h";
        break;
    }
}

⚠️ Fun note: bcrypt truncates input longer than 72 characters.


HTTP Parameter Pollution

parameter_pollution

Parameter parsing and prioritization vary based on the underlying web technology in use. This may be exploited in various ways.

Common examples are in PHP/Javascript that are dynamically typed and inferring the type of the parameter based on the use of [].

// PHP
"https://example.com/?username=abc"     # String
"https://example.com/?username[]=abc"   # Array
// JavaScript
"https://example.com/?username=abc"     # String
"https://example.com/?username[0]=abc"  # Array

This can be very helpful to exploit type juggling such as explained here for PHP while in JS you can bypass size checks with that.


Python Werkzeug Debug Pin (Flask)

werkzeug flask_development_server

Flask is built on top of the Werkzeug webserver. It has a debugger that can be exploited to gain RCE. Try to access /console to see if it's enabled. You will be prompted for a pin.

According to the documentation: "If an incorrect PIN is entered too many times the server needs to be restarted.".

We can compute the PIN on our machine assuming we manage to get the necessary information from the target.

private_bits = [
    # /proc/net/arp (ex: eth0) => /sys/class/net/eth0/address
    str(int('00:00:00:00:00:00'.replace(':', ''), 16)),
    'machine_identifier'
]

To get the machine identifier, concatenate the content of /etc/machine-id or /proc/sys/kernel/random/boot_id with the last value of the first line (slash-separated, can be empty) of /proc/self/cgroup.

probably_public_bits = [
    # User running the app (/etc/passwd + /proc/self/status)
    'username_here',
    
    # Usually left unchanged
    'flask.app', 'Flask',
    # 'werkzeug.debug', 'DebuggedApplication',
    # 'flask.app', 'wsgi_app'
    
    # We are looking for "flask/app.py"
    # /usr/local/lib/pythonX/site-packages/
    # /home/<username>/.local/lib/pythonX/site-packages/
    '/usr/local/lib/python3.10/site-packages/flask/app.py'
]

Refer to hacktricks for the script, while you need to update the two variables for the PIN to be the correct one.

πŸ“š See also: werkzeug_debug_rce (secret exposed in HTML?)


Random Notes

Execution After Redirect (EAR)

http_improper_redirect

It occurs when a user is redirected, but if they don't follow the redirection, then they can still access the page.

In PHP, it would occur if the developper forgot to call exit as any code after the redirecting is still executed.

You can use ncat to read a page without being redirected.

$ nc domain 80
GET /URI HTTP/1.1
Host: domain

PHP Register Globals

php_register_globals

When using register_globals, global variables such as $_GET['param'] are automatically mapped to a global variable such as $param.

It applies to $_GET, $_POST, $_COOKIE, $_REQUEST, etc.

This feature is deprecated since PHP 5.3.0, while the extract(ARRAY) function is still present and maybe still be used.

Exploitation

  • Set $token: use URL/?token=xxx.
  • Set $_SESSION['key']: use URL/?_SESSION[key]=xxx.

Client-Side Logic

http_post

While uncommon, if the logic is client-side and only the result is sent to the server, we can do whatever we want.


PHP Remote Xdebug

php_remote_xdebug

Xdebug allows developers to remotely debug a PHP web application. This gist provide multiple interesting details. If remote_connect_back, anyone can run PHP commands on the server.

  • Create a file in your IDE (ex: index.php, any name)
  • Add code (e.g. <?php\nphpinfo();) and a breakpoint
  • Listen for PHP debug connections (ex: PHPStorm>Bottom-right icon)
  • Load the page supporting Xdebug
  • Go back to your IDE and enjoy

For PHPStorm, refer to the debug tool window overview.


πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

  • mt_rand: known to have a lack of entropy. PoC where found. Sometimes exploitable. See: Snowflake.