{"id":20163483,"url":"https://github.com/sjinks/php-upload-logger","last_synced_at":"2025-04-10T00:40:44.310Z","repository":{"id":15422004,"uuid":"78113393","full_name":"sjinks/php-upload-logger","owner":"sjinks","description":"PHP extension to track, scan, and log all file uploads","archived":false,"fork":false,"pushed_at":"2023-09-08T10:24:24.000Z","size":27,"stargazers_count":3,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-24T02:22:40.046Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sjinks.png","metadata":{"funding":{"custom":["https://www.paypal.com/donate/?hosted_button_id=SAG6877JDJ3KU","https://send.monobank.ua/jar/7rosVfiwKM"]},"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-05T13:02:58.000Z","updated_at":"2023-09-04T06:41:36.000Z","dependencies_parsed_at":"2024-11-14T00:39:57.029Z","dependency_job_id":null,"html_url":"https://github.com/sjinks/php-upload-logger","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjinks%2Fphp-upload-logger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjinks%2Fphp-upload-logger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjinks%2Fphp-upload-logger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjinks%2Fphp-upload-logger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sjinks","download_url":"https://codeload.github.com/sjinks/php-upload-logger/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137996,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T00:29:46.090Z","updated_at":"2025-04-10T00:40:44.304Z","avatar_url":"https://github.com/sjinks.png","language":"C","funding_links":["https://www.paypal.com/donate/?hosted_button_id=SAG6877JDJ3KU","https://send.monobank.ua/jar/7rosVfiwKM"],"categories":[],"sub_categories":[],"readme":"# php-upload-logger\n\n[![Build Status](https://travis-ci.org/sjinks/php-upload-logger.png?branch=master)](https://travis-ci.org/sjinks/php-upload-logger)\n[![Coverity Scan Build Status](https://scan.coverity.com/projects/11356/badge.svg)](https://scan.coverity.com/projects/sjinks-php-upload-logger)\n\nPHP extension to track and log all file uploads.\n\n## About\n\nThis is a tool that I use myself to track malicious file uploads.\nIf someone else finds it useful, well, I will be glad.\n\nSometimes, looking into `/tmp`, I see something like this:\n\n```\n'/tmp/php2X0Uup'\n# Known exploit = [Fingerprint Match] [PHP POST Exploit [P0806]]\n\n'/tmp/php5E9xaH'\n# Known exploit = [Fingerprint Match] [PHP POST Exploit [P0892]]\n\n'/tmp/php65shDi'\n# Known exploit = [Fingerprint Match] [PHP POST Exploit [P0892]]\n\n'/tmp/php9v1E5t'\n# Known exploit = [Fingerprint Match] [PHP POST Exploit [P0892]]\n\n'/tmp/phpA2JC27'\n# Known exploit = [Fingerprint Match] [PHP POST Exploit [P0892]]\n…\n```\n\nThese files are leftovers from vulnerability scans: a scan tool\ntried to exploit a suspected vulnerability somewhere, and tried\nto upload a malicious file. But because the component the attacker\ntried to exploit was not vulnerable and rejected the upload,\nthe file the attacker had tried to upload was left in `/tmp`\n(I still wonder why PHP does not delete such files after the end\nof the request).\n\nIt was always interesting for me to know who upload such files,\nand what component they are trying to exploit. This is how this\nextension was born :-)\n\n## Installation\n\n```bash\nphpize \u0026\u0026 ./configure \u0026\u0026 make \u0026\u0026 sudo make install\n```\n\nTested to work under PHP 7.0 and 7.1. It will not compile for PHP 5,\nbut it should be trivial to fix.\n\n## Configuration\n\nRight now there are three configuration directives (to be added to\n`php.ini`) controlling the behavior of the extsnsion (both are\n`PHP_INI_PREDIR`):\n\n  * `ul.enabled` (boolean, default is `false`): whether Upload\n  Logger is enabled.\n  * `ul.dir` (string, default is `/tmp`): directory where Upload\n  Logger writes its log files.\n  * `ul.verification_script` (string, default is empty): path to\n  the verification script to be run for every uploaded file.\n\nLog files are named `UID`.log, where `UID` is the ID of the user\nPHP runs as. Log files are created with 0600 permissions.\n\nThe reason why there is a separate file for every user is that there\ncould be multiple PHP processes running as different users (think of\nphp-fpm or [chuid](https://github.com/sjinks/php-chuid)): in this case\nthe log file either needs to be world writable (I hate this), or it might\nbe necessary to add all users to the single group (and use 0660\npermissions), but this complicates the configuration.\n\n## Principle of Operation\n\nUpload Logger sets its own callback for `php_rfc1867_callback` (it is called\nwhen PHP parses multipart/form-data and handles file upload). It listens for\ntwo events:\n\n  * `MULTIPART_EVENT_FILE_START`: this one is triggered when PHP starts to\n  process the upload. At this moment we know the original filename of the\n  uploaded file.\n  * `MULTIPART_EVENT_FILE_END`: this one is triggered when PHP finishes to\n  process the uploaded file. At this moment we know the size of the file, and\n  the name of the temporary file to which the uploaded file was written\n  (it corresponds to `$_FILES['userfile']['tmp_name']`).\n\nThe most common way to save the uploaded file is [`move_uploaded_file`](http://php.net/manual/en/function.move-uploaded-file.php).\nTherefore Upload Logger intercepts this function, and logs the temporary\nand the new filenames.\n\n## Log File\n\nThis is how the log file looks like:\n\n```\n[2017-01-05 13:43:05] File ID: 27008_139636915363904_1\nFilename: 3 - 1.JPG\nREQUEST_URI: /wp-admin/async-upload.php\nPATH_TRANSLATED: /home/***/wp-admin/async-upload.php\nQUERY_STRING:\nREMOTE_ADDR: 1.2.3.4\nHTTP_X_FORWARDED_FOR: 5.6.7.8\n\n\n[2017-01-05 13:43:05] File ID: 27008_139636915363904_1\nTemporary filename: /tmp/phpJvX3jY\nUpload status: 0\n\n\n[2017-01-05 13:43:05]\nREMOTE_ADDR: 1.2.3.4\nHTTP_X_FORWARDED_FOR: 5.6.7.8\nmove_uploaded_file: old=/tmp/phpJvX3jY, new=/home/*/wp-content/uploads/2017/01/3-1.jpg, status=SUCCESS\n```\n\n*The first block* is written during the `MULTIPART_EVENT_FILE_START` phase.\n\n`File ID` is used to distinguish between different threads and processes\nand consists of two (NTS) or three (ZTS) fields separarted with `_`:\n`PID_TID_CTR`\n\n  * `PID` is the process ID (useful for, say, php-fpm or php-cgi);\n  * `TID` is thread ID (only if PHP was compiled with ZTS support);\n  * `CTR` incrementing counter for the given `PID` and `TID`.\n\n`Filename` is the original filename.\n\n`REQUEST_URI`, `PATH_TRANSLATED`, `QUERY_STRING` are the request URI,\nthe name of the file that is meant to handle the upload, and the\nquery string respectively. Note that these variables are read directly\nfrom the SAPI module, and therefore they cannot be \"overridden\" by\nreplacing a corresponding `$_SERVER` variable (moreover, when PHP handles\nuploads, `$_SERVER` variables are not yet available anyway).\n\n`REMOTE_ADDR` and `HTTP_X_FORWARDED_FOR` (and `HTTP_CF_CONNECTING_IP`)\ncontain the remote address of the uploader. These data are gotten from\nthe SAPI, and therefore may not always be available (ie, when the PHP\nCLI binary runs in server mode, `REMOTE_ADDR` et al are not available).\n\n*The second block* is written during the `MULTIPART_EVENT_FILE_END` phase.\n\n`Temporary filename` is the name of the temporary file to which PHP has\nwritten the uploaded data.\n\n`Upload status` is the indication whether the file has been successully\nuploaded (0). The codes are available [here](http://php.net/manual/en/features.file-upload.errors.php).\n\n*The third block* is written during execution of `move_uploaded_file()`.\nIt also contains the remote address because sometimes it may not be possible\nto get it during the `MULTIPART_EVENT_FILE_START` phase.\n\nThe `move_uploaded_file` line:\n\n```\nmove_uploaded_file: old=TEMPORARY_FILENAME, new=NEW_FILENAME, status=SUCCESS_or_FAILURE\n```\n\n`old` and `new` are the original arguments passed to `move_uploaded_file` (ie,\n`move_uploaded_file($old, $new)`) and status (`SUCCESS` or `FAILURE`) is the result\nreturned by `move_uploaded_file` (`SUCCESS` obviously corresponds to `true`).\n\n## Upload Verification Script\n\nUpload Logger can run custom verification script for every uploaded file.\nThe name of the script is specified in the `ul.verification_script`\nconfiguration directive.\n\nThe script must be an executable (script or binary) file.\n\n**WARNING:** failure to locate or execute the script leads to rejection of\nthe uploaded file.\n\nThe verification script is invoked as follows:\n\n```bash\n/path/to/script /path/to/uploaded/file 2\u003e\u00261\n```\n\nThat is, the name of the file to check is given as the first argument.\n\nIf the script has no objections as to the uploaded file, it should print\nthe plus (`+`) sign. Everything else is treated as the objection.\n\n### Sample Verification Script\n\nThe script below assumes that you have the ClamAV daemon installed and\nrunning, and `clamdscan` binary is available.\n\n```bash\n#!/bin/sh\n\n# This normally should not happen unless the script is invoked manually\nif [ \"x$1\" = \"x\" ]; then\n    echo '-'\n    exit 10\nfi\n\n# Scan the file with ClamAV\n# Return code of 0 means the file is possibly clean\nOUT=$(clamdscan --fdpass --infected --no-summary \"$1\")\nRC=$?\n\nif [ $RC -eq 0 ]; then\n    # Allow the file\n    echo '+'\nelse\n    # Reject the file and print the scan log\n    echo '-'\n    echo \"$OUT\"\nfi\n\nexit $RC\n\n```\n\nPossible output when the [EICAR Test File](https://en.wikipedia.org/wiki/EICAR_test_file)\nis uploaded:\n\n```\n[2017-01-06 11:20:31] File ID: 27008_139636915363904_1\nFilename: eicar.com\nREQUEST_URI: /index.php\nPATH_TRANSLATED: /var/www/php-upload-logger-test/index.php\nQUERY_STRING: N/A\nREMOTE_ADDR: N/A\n\n\n[2017-01-06 11:20:31] File ID: 27008_139636915363904_1\nTemporary filename: /tmp/phpqIjGWR\nUpload status: 0\nThe file is disallowed by the verification script:\n-\n/tmp/phpqIjGWR: Eicar-Test-Signature FOUND\n\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjinks%2Fphp-upload-logger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsjinks%2Fphp-upload-logger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjinks%2Fphp-upload-logger/lists"}