This is the code for the paper: PFORTIFIER: Mitigating PHP Object Injection through Automatic Patch Generation.
The presentation of our work was part of the IEEE S&P 2025 event.
Please cite the above paper if you use our code.
The code is released under the GPLv3.
- Automated PHP POP chain discovery
- Automatic patch generation
- Neo4j graph query-assisted discovery
- Fixed parsing issues with new PHP syntax in phply
Payload auto-generation (deprecated)
- Install Python
- Install py2neo:
pip install py2neo - (Optional) Install Neo4j and set the password to "password"
- Download the PHP code dataset from [https://github.com/CyanM0un/PFortifier_DataSet]. Keep the default configurations in
config.py, set the target code path (php_prog_root), and runMain.py. - After scanning and patch generation, results will be saved under the
resultdirectory:pop_chains.json: Discovered POP chainspatch.json: Generated patches for the POP chainspatch_collect.json: De-duplicated patches for manual reviewunable2patch_entry.json: Entry points where suggestions are generated instead of direct patches
- Configure
config.pyand runMain.py
php_prog_root: Root directory of the PHP programgc_switch: Enable garbage collection (reduces memory usage but slows scanning)patch_generate: Enable patch generationgraph_gen: Enable Neo4j graph database collectionuse_pm_summary: Enable summary acceleration mode (recommended; see paper for details)skip_overdetected: Skip over-detected chains (filters chains with identical entry-sink pairs in PM mode)filter_sink: Record each entry-sink pair only onceuse_cache: Enable cache for subsequent scans on the same codebaseexclude_die_wakeup: Exclude classes withdie()in__wakeupentry_func_li: Entry functions (e.g.,__destruct)max_pm_length: Maximum PM chain length (PM mode only)max_normal_length: Maximum chain length (all methods/functions). Tip: Set one to 999999 and limit the othereach_entry_early_stop_num: Maximum chains per entry (prevents excessive logging)entry_depth: Entry chain depth (controls initial chain segments treated as entries)early_stop_num: Global maximum chain count (stops logging if exceeded)
Recommended configurations for different goals:
- POP Chain Discovery:
each_entry_early_stop_num: 999999entry_depth: 1-3 (adjust based on codebase size)early_stop_num: 3000+
- Patch Generation:
each_entry_early_stop_num: 5entry_depth: 3early_stop_num: 1000- Disable PM summary for large frameworks
General Tips:
max_pm_length< 6 (balances coverage and speed)max_normal_length= 9 (faster for large frameworks)- Deeper
entry_depthenhances entry discovery for patching
- Comment out irrelevant PHP code as needed
- Neo4j queries may reveal more chains than direct scanning
- For frameworks with many sinks, disable PM summary and set
each_entry_early_stop_num= 5
Find deserialization chains of length 3-5:
MATCH p=(m1:Method{MethodName:"__destruct"})-[*3..5]->(m2{IsSink:TRUE}) RETURN p LIMIT 25- PFortifier preserves case sensitivity for classes/namespaces to align with
vendor/autoload.phpbehavior, despite PHP's case insensitivity.