{"id":78,"date":"2026-09-17T09:08:38","date_gmt":"2026-09-17T09:08:38","guid":{"rendered":"https:\/\/microupdate.co.uk\/articles\/?p=78"},"modified":"2026-09-17T09:10:42","modified_gmt":"2026-09-17T09:10:42","slug":"detect-suspicious-activity-in-wordpress-access-logs","status":"publish","type":"post","link":"https:\/\/microupdate.co.uk\/articles\/wordpress\/log-files\/detect-suspicious-activity-in-wordpress-access-logs\/","title":{"rendered":"Detect Suspicious Activity in WordPress Access Logs"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><code>log_analyser_v5.py<\/code> is a simple single-file Python script for reviewing WordPress web server access logs. It scans Apache-style access logs and highlights requests that match common indicators of suspicious or hostile activity.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The script is intentionally small and easy to inspect. It does not require external Python libraries, does not need a database, and writes its reports into the same folder as the logs being analysed.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/github.com\/jcamp\/logfile_malware_analyser\/raw\/main\/log_analyser_v5.py\">Download log_analyser_v5.py<\/a><\/div>\n\n\n\n<div class=\"wp-block-button is-style-outline is-style-outline--1\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/github.com\/jcamp\/logfile_malware_analyser\">View the GitHub repository<\/a><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">What the Script Does<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>log_analyser_v5.py<\/code> walks through the folder where the script is located and scans files ending in <code>.log<\/code>. For each matching Apache-style access log entry, it checks the requested URL against a short list of security rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a rule matches, the script records the finding with a severity, rule ID, explanation, recommended action, source log file, line number, IP address, request method, status code, and URL.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It scans local <code>.log<\/code> files.<\/li>\n\n\n\n<li>It looks for common WordPress attack patterns.<\/li>\n\n\n\n<li>It writes a readable text report.<\/li>\n\n\n\n<li>It writes a pipe-separated values report for spreadsheet use.<\/li>\n\n\n\n<li>It uses rule IDs so findings can be tracked consistently.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Rules Included in Version 5<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Rule ID<\/th><th>Severity<\/th><th>Finding<\/th><th>What it means<\/th><\/tr><\/thead><tbody><tr><td>CR001<\/td><td>Critical<\/td><td>PHP executed from uploads<\/td><td>A <code>.php<\/code> file was requested from <code>\/wp-content\/uploads\/<\/code>. Upload folders are usually writable and should not contain executable PHP.<\/td><\/tr><tr><td>CR002<\/td><td>Critical<\/td><td>Known web shell<\/td><td>The requested URL ends with a filename commonly associated with web shells, such as <code>wso.php<\/code>, <code>c99.php<\/code>, <code>r57.php<\/code>, or <code>shell.php<\/code>.<\/td><\/tr><tr><td>HI001<\/td><td>High<\/td><td>Log4Shell probe<\/td><td>The requested URL contains <code>jndi<\/code> or <code>ldap:\/\/<\/code>, which are strings commonly used in Log4Shell exploit probes.<\/td><\/tr><tr><td>HI002<\/td><td>High<\/td><td>Sensitive file probe<\/td><td>The request attempts to access sensitive files such as <code>.env<\/code> or <code>.git\/config<\/code>.<\/td><\/tr><tr><td>HI003<\/td><td>High<\/td><td>Path traversal<\/td><td>The URL contains traversal patterns such as <code>..\/<\/code> or <code>%2e%2e<\/code>, which may indicate an attempt to reach files outside the intended web path.<\/td><\/tr><tr><td>ME001<\/td><td>Medium<\/td><td>XML-RPC<\/td><td>The request targets <code>\/xmlrpc.php<\/code>, a WordPress endpoint often targeted for brute-force or automated abuse.<\/td><\/tr><tr><td>ME002<\/td><td>Medium<\/td><td>WordPress login<\/td><td>The request targets <code>\/wp-login.php<\/code>, the standard WordPress login page.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Known Safe Path Handling<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Version 5 includes a known safe path list for the web shell rule. The following WordPress core path is ignored by the known web shell check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/wp-includes\/text\/diff\/engine\/shell.php<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This matters because the filename <code>shell.php<\/code> can look suspicious in isolation, but this specific path exists in WordPress core and should not be treated the same way as a random uploaded shell file.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to Use It<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Download the script and place it in the same folder as the log files you want to analyse.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run it with Python 3:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python log_analyser_v5.py<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On some systems, you may need to run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 log_analyser_v5.py<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The script prints the folder being scanned and then writes the report files into that same folder.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Input Log Format<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The script is designed for Apache combined-style access logs. A typical line looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>203.0.113.10 - - &#91;20\/Jul\/2026:12:34:56 +0000] \"GET \/wp-login.php HTTP\/1.1\" 200 1234 \"-\" \"Mozilla\/5.0\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Only lines that match the expected access log pattern are analysed. Lines that do not match are skipped.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Output Files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The script creates two output files:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>log_analysis_report.txt<\/code> &#8211; a human-readable report for review.<\/li>\n\n\n\n<li><code>log_analysis_report.psv<\/code> &#8211; a pipe-separated report suitable for spreadsheet import or further processing.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The PSV report uses <code>|<\/code> as the separator and includes the Rule ID as the first column.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Interpreting the Text Report<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The text report starts with a summary. This gives a quick count of each finding type grouped by severity.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SUMMARY\n------------------------------------------------------------------------\nCRITICAL      2  PHP executed from uploads\nHIGH          5  Sensitive file probe\nMEDIUM       18  WordPress login<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">After the summary, detailed findings are grouped by severity. Each finding includes the rule ID, source file, line number, IP address, URL, details explaining why the rule matched, and a suggested action.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Rule ID : CR001\nFinding : PHP executed from uploads\nDate    : 20\/Jul\/2026 12:34:56 +0000\nFile    : access.log\nLine    : 42\nIP      : 203.0.113.10\nMethod  : GET\nStatus  : 200\nURL     : \/wp-content\/uploads\/example.php\nDetails : The URL starts with \/wp-content\/uploads\/ and ends with .php, indicating that a PHP file was requested from the writable uploads folder.\nAction  : Verify the file exists and identify which plugin created it.<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">How to Read the Severity Levels<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Critical findings<\/strong> should be investigated first. These may indicate a PHP file running from uploads or a request for a known web shell filename. Check whether the file exists on the server, when it was created, and whether it is legitimate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>High findings<\/strong> usually indicate active probing or exploit attempts. These do not always mean the site was compromised, but they show that the site was targeted for specific weaknesses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Medium findings<\/strong> often show common WordPress attack surface activity, such as login page requests or XML-RPC access. A few entries may be normal. Large volumes from the same IP address may suggest brute-force or automated scanning.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Practical Investigation Steps<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Review all critical findings first.<\/li>\n\n\n\n<li>Check whether any suspicious PHP files actually exist on the server.<\/li>\n\n\n\n<li>Look at repeated requests from the same IP address.<\/li>\n\n\n\n<li>Compare suspicious timestamps with file modification times on the server.<\/li>\n\n\n\n<li>Confirm whether requests returned successful status codes such as <code>200<\/code>.<\/li>\n\n\n\n<li>Review WordPress users, plugins, themes, and recent file changes if critical findings are present.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Limitations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This script is a triage tool. It identifies suspicious access log entries, but it does not prove compromise by itself. A request for a suspicious URL may have failed, may have returned a 404, or may simply show automated internet scanning.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most important results are usually critical findings with successful status codes, repeated activity from the same IP address, or requests that match files present on the server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Download and Source Code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You can download the script directly here:<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/github.com\/jcamp\/logfile_malware_analyser\/raw\/main\/log_analyser_v5.py\">Download log_analyser_v5.py<\/a><\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The GitHub repository is available at <a href=\"https:\/\/github.com\/jcamp\/logfile_malware_analyser\">https:\/\/github.com\/jcamp\/logfile_malware_analyser<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A simple single-file Python script for reviewing WordPress web server access logs<\/p>\n","protected":false},"author":1,"featured_media":79,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_titles_title":"Detect Suspicious Activity in WordPress Access Logs","_seopress_titles_desc":"A simple single-file Python script for reviewing WordPress web server access logs","_seopress_robots_index":"","_seopress_robots_follow":"","_seopress_robots_imageindex":"","_seopress_robots_snippet":"","_seopress_robots_primary_cat":"11","_seopress_robots_breadcrumbs":"","_seopress_robots_freeze_modified_date":"","_seopress_robots_custom_modified_date":"","_seopress_robots_canonical":"","_seopress_social_fb_title":"","_seopress_social_fb_desc":"","_seopress_social_fb_img":"","_seopress_social_fb_img_attachment_id":0,"_seopress_social_fb_img_width":0,"_seopress_social_fb_img_height":0,"_seopress_social_twitter_title":"","_seopress_social_twitter_desc":"","_seopress_social_twitter_img":"","_seopress_social_twitter_img_attachment_id":0,"_seopress_social_twitter_img_width":0,"_seopress_social_twitter_img_height":0,"_seopress_redirections_value":"","_seopress_redirections_enabled":"","_seopress_redirections_enabled_regex":"","_seopress_redirections_logged_status":"","_seopress_redirections_param":"","_seopress_redirections_type":0,"_seopress_analysis_target_kw":"","_seopress_news_disabled":"","_seopress_video_disabled":"","_seopress_video":[],"_seopress_pro_schemas_manual":[],"_seopress_pro_rich_snippets_disable_all":"","_seopress_pro_rich_snippets_disable":[],"_seopress_pro_schemas":[],"footnotes":""},"categories":[11,7,6],"tags":[],"class_list":["post-78","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-log-files","category-malware","category-wordpress"],"_links":{"self":[{"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/posts\/78","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/comments?post=78"}],"version-history":[{"count":2,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/posts\/78\/revisions"}],"predecessor-version":[{"id":81,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/posts\/78\/revisions\/81"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/media\/79"}],"wp:attachment":[{"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/media?parent=78"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/categories?post=78"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microupdate.co.uk\/articles\/wp-json\/wp\/v2\/tags?post=78"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}