Using Multiplier to identify bugs similar to the vulnerability identified in pwning.systems.
Clone PHP source and checkout the vulnerable commit:
$ git clone [email protected]:php/php-src.git
$ cd php-src
$ git checkout 414d5620
The vulnerable implicit cast can be confirmed in
ext/filter/logical_filters.c
:
...
507: static int _php_filter_validate_domain(char * domain, int len, zend_long flags) /* {{{ */
...
561: if (!_php_filter_validate_domain(Z_STRVAL_P(value), Z_STRLEN_P(value), flags)) {
...
Note that _php_filter_validate_domain()
takes a second argument of type
int
, but that on line 561, it is passed a size_t
(from Z_STRLEN_P()
).
Install deps (Ubuntu 20.04) and generate compile_commands.json
:
$ sudo apt install -y pkg-config build-essential autoconf bison re2c \
libxml2-dev libsqlite3-dev
$ ./buildconf
$ ./configure
$ bear make -j `cat /proc/cpuinfo | grep processor | wc -l`
Modify compile_commands.json
to only contain entry for the compilation unit
for the ext/filter/logical_filters.c
file (e.g. remove lines 2-1878,
1915-2847).
Start Multiplier indexer:
$ ./bin/mx-index --target <path_to>/php-src/compile_commands.json --db /tmp/php.db --workspace_dir /tmp/php.ws --show_progress
This could take some time. When indexing is done, the indexer will exit.
Identify the ID for the ext/filter/logical_filters.c
file (note that ID
values will differ in different workspaces):
$ ./bin/mx-list-files --db /tmp/php.db
208 /home/huckfinn/tob/targets/php-src/TSRM/TSRM.h
260 /home/huckfinn/tob/targets/php-src/Zend/zend.h
90 /home/huckfinn/tob/targets/php-src/Zend/zend_API.h
156 /home/huckfinn/tob/targets/php-src/Zend/zend_alloc.h
155 /home/huckfinn/tob/targets/php-src/Zend/zend_alloc_sizes.h
<snip>
265 /home/huckfinn/tob/targets/php-src/ext/filter/logical_filters.c
<snip>
Identify the fragment ID of the functions of interest:
$ ./bin/mx-list-functions --db /tmp/php.db --file_id 265
265 65536 495 def php_filter_validate_mac
265 65537 268435951 def php_filter_validate_ip
265 65538 536871407 def _php_filter_validate_ipv6
265 65539 805306863 def _php_filter_validate_ipv4
265 65540 1073742319 def php_filter_validate_email
265 65541 1342177775 def php_filter_validate_url
265 65542 1610613231 def is_userinfo_valid
265 65543 1879048687 def php_filter_validate_domain
265 65544 2147484143 def _php_filter_validate_domain
265 65545 2415919599 def php_filter_validate_regexp
265 65546 2684355055 def php_filter_float
265 65546 2684477559 decl __builtin_constant_p
265 65546 2684481655 decl __builtin_isfinite
265 65547 2952790511 def php_filter_boolean
265 65548 3221225967 def php_filter_int
265 65549 3489661423 def php_filter_parse_hex
265 65550 3758096879 def php_filter_parse_octal
265 65551 4026532335 def php_filter_parse_int
265 65552 4294967415 decl _php_filter_validate_ipv6
Find sketchy casts in the php_filter_validate_domain()
function:
$ ./bin/mx-find-sketchy-casts --db /tmp/php.db --fragment_id 65543
( ( * ( value ) ) . value . str ) -> len
U_LONG -> INT
(Recognize that this what the expression looks like after macro expansions have
been applied. Can confirm this by printing the tokens in the fragment, and
notice that the first and second arguments to _php_filter_validate_domain()
have been expanded.)
$ ./bin/Examples/mx-print-fragment -fragment_id 65543
void php_filter_validate_domain ( zval * value , zend_long flags , zval * option_array , char * charset ) { if ( ! _php_filter_validate_domain ( ( ( * ( value ) ) . value . str ) -> val , ( ( * ( value ) ) . value . str ) -> len , flags ) ) { if ( ( executor_globals . exception ) ) { return ; } else if ( flags & 0x8000000 ) { zval_ptr_dtor ( value ) ; do { ( * ( value ) ) . u1 . type_info = 1 ; } while ( 0 ) ; } else { zval_ptr_dtor ( value ) ; do { ( * ( value ) ) . u1 . type_info = 2 ; } while ( 0 ) ; } return ; } }⏎
(Note: can also show the more readable unparsed tokens with the -unparsed
flag to mx-print-fragment
.)
Get the entity ID of the php_filter_validate_domain()
function:
$ ./bin/Examples/mx-list-functions -fragment_id 65543
265 65543 1879048687 def php_filter_validate_domain
Get the call hierarchy of the php_filter_validate_domain()
function using its
entity ID as reference:
$ ./bin/Examples/mx-print-call-hierarchy -entity_id 1879048687
php_filter_validate_domain 265 65543 1879048687 FUNCTION
This isn't helpful, because the only file indexed so far has been the
compilation unit for ext/filter/logical_filters.c
. Index the rest of the PHP
source to make this useful (e.g. use the mx-import
command as above, but
provide it with the entire unmodified compile_commands.json
file generated by
bear
).
The vulnerable implicit cast identified in [pwning.systems]
(https://pwning.systems/posts/php_filter_var_shenanigans/) is in the function
php_filter_validate_domain()
. It passes the unsigned long parameter
Z_STRLEN_P(value)
to the function _php_filter_validate_domain()
, which
takes an int
parameter. Z_STRLEN_P
is actually, after some macro
expansions, a derefence of a size_t len
field of an object of type
_zend_string
, defined in Zend/zend_string.h
. So to summarize,
the value of Z_STRLEN_P(value)
is the _zend_string.len
field.
We can use mx-find-sketchy-casts
to identify all references to the
_zend_string.len
field that are used as arguments to call expressions with
implicit casts, and possibly more instances of the vulnerability.
(Ensure that all of PHP is indexed before this example.)
To get the entity ID of the _zend_string.len
field, first find the file ID
where it is defined, and then the entity ID of the field:
$ ./bin/Examples/mx-list-files | grep zend_types
228 /home/huckfinn/tob/targets/php-src/Zend/zend_types.h
$ ./bin/Examples/mx-list-structures -file_id 228 | grep -A 5 _zend_string
228 68540 806380110467 _zend_string
806380114399 gc
806380118495 h
806380122591 len
806380126687 val
(The entity ID of len
is the 806380122591 value in the len
row.)
Identify other possible instances of the vulnerable code pattern with this entity:
$ ./bin/Examples/mx-find-sketchy-casts -entity_id 806380122591 -show_locations
/home/huckfinn/tob/targets/php-src/Zend/zend_language_scanner.c
file ID: 1145 frag ID: 7 U_LONG -> INT ( copy ) -> len
/home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c:425:20
file ID: 353 frag ID: 71077 U_LONG -> INT ( ( retval ) . value . str ) -> len
/home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c:231:4
file ID: 353 frag ID: 71090 U_LONG -> INT 2 * ( unquoted ) -> len + 3
/home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c:193:8
file ID: 353 frag ID: 71099 U_LONG -> INT ( sql ) -> len
/home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_statement.c:160:41
file ID: 493 frag ID: 75101 U_LONG -> INT ( ( * ( parameter ) ) . value . str ) -> len
/home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_statement.c:183:45
file ID: 493 frag ID: 75101 U_LONG -> INT ( ( * ( parameter ) ) . value . str ) -> len
/home/huckfinn/tob/targets/php-src/ext/filter/logical_filters.c:561:9
file ID: 583 frag ID: 79319 U_LONG -> INT ( ( * ( value ) ) . value . str ) -> len
/home/huckfinn/tob/targets/php-src/ext/dom/node.c:192:12
file ID: 648 frag ID: 82398 U_LONG -> INT ( str ) -> len + 1
/home/huckfinn/tob/targets/php-src/ext/dom/attr.c:154:4
file ID: 987 frag ID: 92287 U_LONG -> INT ( str ) -> len + 1
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:1845:14
file ID: 1001 frag ID: 92511 U_LONG -> INT ( sql ) -> len
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:1549:38
file ID: 1001 frag ID: 92518 U_LONG -> INT ( buffer ) -> len
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:1568:34
file ID: 1001 frag ID: 92518 U_LONG -> INT ( str ) -> len
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:840:20
file ID: 1001 frag ID: 92544 U_LONG -> INT ( str ) -> len
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:685:18
file ID: 1001 frag ID: 92545 U_LONG -> INT ( sql ) -> len
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:576:18
file ID: 1001 frag ID: 92547 U_LONG -> INT ( sql ) -> len
/home/huckfinn/tob/targets/php-src/ext/sqlite3/sqlite3.c:522:14
file ID: 1001 frag ID: 92548 U_LONG -> INT ( sql ) -> len
/home/huckfinn/tob/targets/php-src/ext/dom/processinginstruction.c:131:4
file ID: 1066 frag ID: 95143 U_LONG -> INT ( str ) -> len + 1
/home/huckfinn/tob/targets/php-src/ext/dom/characterdata.c:73:4
file ID: 1151 frag ID: 98522 U_LONG -> INT ( str ) -> len + 1
Current work: triage these instances to determine if any may be vulnerabilities.
mx-find-sketchy-casts identified an implicit cast of the argument to
sqlite3_snprintf()
in the function sqlite_handle_quoter()
in
ext/pdo_sqlite/sqlite_driver.c
.
To identify where sqlite_handle_quoter()
gets called:
$ ./bin/Examples/mx-list-files | grep sqlite_driver.c
746 /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c
$ ./bin/Examples/mx-list-functions -file_id 746 | grep sqlite_handle_quoter
746 81935 4402073043439 sqlite_handle_quoter def
$ ./bin/Examples/mx-print-call-hierarchy -entity_id 4402073043439
sqlite_handle_quoter 746 81935 4402073043439 FUNCTION /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c 227 21
sqlite_methods 746 81917 4397241205475 VAR /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c 720 37
pdo_sqlite_handle_factory 746 81914 4396435898863 FUNCTION /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c 792 12
pdo_sqlite_driver 746 77087 3100697952995 VAR /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c 852 20
zm_shutdown_pdo_sqlite 904 87473 5888668598767 FUNCTION /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/pdo_sqlite.c
pdo_sqlite_module_entry 904 87475 5889205469923 VAR /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/pdo_sqlite.c 38 19
<snip>
$ ./bin/Examples/mx-list-uses -entity_id 4402073043439 -show_locations -highlight_user
<snip>
746 81917 4397241238871 DECL_REF_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo_sqlite/sqlite_driver.c 724 4
DECLARATION
FOUND_DECLARATION
REFERENCED_DECLARATION_OF_CALLEE
+---------------------------------------------
720 | static const struct pdo_dbh_methods sqlite_methods = {
721 | sqlite_handle_closer,
722 | sqlite_handle_preparer,
723 | sqlite_handle_doer,
724 | sqlite_handle_quoter,
725 | sqlite_handle_begin,
726 | sqlite_handle_commit,
727 | sqlite_handle_rollback,
728 | pdo_sqlite_set_attr,
729 | pdo_sqlite_last_insert_id,
730 | pdo_sqlite_fetch_error_func,
731 | pdo_sqlite_get_attribute,
732 | NULL, /* check_liveness: not needed */
733 | get_driver_methods,
734 | pdo_sqlite_request_shutdown,
735 | NULL, /* in transaction, use PDO's internal tracking mechanism */
736 | pdo_sqlite_get_gc
737 | }
<snip>
This shows that the sqlite_handle_quoter
is registered as a function pointer
in the sqlite_methods
struct, which is of type pdo_dbh_methods
. To
determine where the uses of the sqlite_handle_quoter
function pointer are, we
look up the field entity id of the pdo_dbh_methods
struct, which is defined
in php_pdo_driver.h
:
$ ./bin/Examples/mx-list-files | grep php_pdo_driver.h
384 /home/huckfinn/tob/targets/php-src/ext/pdo/php_pdo_driver.h
$ ./bin/Examples/mx-list-structures -file_id 384 | grep pdo_dbh_methods -A10
384 71938 1718523789955 pdo_dbh_methods
1718523793887 closer
1718523797983 preparer
1718523802079 doer
1718523806175 quoter
1718523810271 begin
1718523814367 commit
1718523818463 rollback
1718523822559 set_attribute
1718523826655 last_id
1718523830751 fetch_err
$ ./bin/Examples/mx-list-uses -entity_id 1718523806175 -show_locations -highlight_user
<snip>
$ ./bin/Examples/mx-list-uses -entity_id 1718523806175 -show_locations
384 71938 1718523806175 FIELD /home/huckfinn/tob/targets/php-src/ext/pdo/php_pdo_driver.h 296 28
UNDERLYING_DECLARATION
735 75649 2714692465975 IMPLICIT_CAST_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 530 16
REFERENCED_DECLARATION_OF_CALLEE
735 75649 2714692470195 MEMBER_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 530 16
MEMBER_DECLARATION
REFERENCED_DECLARATION_OF_CALLEE
735 75649 2714692957439 CALL_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 549 38
CALLEE_DECLARATION
735 75649 2714692961591 IMPLICIT_CAST_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 549 38
REFERENCED_DECLARATION_OF_CALLEE
735 75649 2714692965811 MEMBER_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 549 38
MEMBER_DECLARATION
REFERENCED_DECLARATION_OF_CALLEE
735 75649 2714693670143 CALL_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 591 42
CALLEE_DECLARATION
735 75649 2714693674295 IMPLICIT_CAST_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 591 42
REFERENCED_DECLARATION_OF_CALLEE
735 75649 2714693678515 MEMBER_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_sql_parser.c 591 42
MEMBER_DECLARATION
REFERENCED_DECLARATION_OF_CALLEE
989 89620 6464999937279 CALL_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_dbh.c
CALLEE_DECLARATION
989 89620 6464999941431 IMPLICIT_CAST_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_dbh.c
REFERENCED_DECLARATION_OF_CALLEE
989 89620 6464999945651 MEMBER_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_dbh.c
MEMBER_DECLARATION
REFERENCED_DECLARATION_OF_CALLEE
989 89620 6465003713847 IMPLICIT_CAST_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_dbh.c 1176 9
REFERENCED_DECLARATION_OF_CALLEE
989 89620 6465003718067 MEMBER_EXPR /home/huckfinn/tob/targets/php-src/ext/pdo/pdo_dbh.c 1176 9
MEMBER_DECLARATION
REFERENCED_DECLARATION_OF_CALLEE