reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: analyzing-webshells
|
||||
description: 'Analyzes suspected webshells (PHP, ASPX/ASP, JSP) by detecting dynamic-execution
|
||||
sinks, input-to-sink data flow, and common encoder/obfuscation chains, then decoding obvious
|
||||
layers to reveal the command interface. Activates for requests to analyze a webshell, identify a
|
||||
PHP/ASPX/JSP backdoor, or deobfuscate server-side malicious scripts.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- webshell
|
||||
- php
|
||||
- aspx
|
||||
- server-side
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1505.003
|
||||
- T1059
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1505.003 Web Shell — https://attack.mitre.org/techniques/T1505/003/'
|
||||
- 'OWASP web shell detection guidance — https://owasp.org/'
|
||||
---
|
||||
|
||||
# Analyzing Webshells
|
||||
|
||||
## When to Use
|
||||
|
||||
- You found a suspicious server-side script (`.php`, `.aspx`, `.jsp`, `.asp`) and need to confirm
|
||||
it is a webshell and understand its command interface.
|
||||
- You are triaging a web-server compromise and want to classify dropped scripts.
|
||||
|
||||
**Do not use** a live web server to execute the script to "test" it — that runs attacker code.
|
||||
Analyze the source statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The suspect script file(s), read inertly.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read the file statically; never deploy or request it on a live server. Defang any URLs.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Detect execution sinks and input flow
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py scan shell.php
|
||||
```
|
||||
|
||||
Flags dynamic-execution sinks (`eval`, `assert`, `system`, `exec`, `passthru`, `Runtime.exec`,
|
||||
ASPX `eval()`/`Process.Start`) and whether request input (`$_GET`/`$_POST`/`Request`) reaches them.
|
||||
|
||||
### Step 2: Identify obfuscation/encoder chains
|
||||
|
||||
Detects `base64_decode`, `gzinflate`, `str_rot13`, `eval(chr(...))`, hex `\x` strings, and
|
||||
`FromBase64String` chains commonly wrapping the payload.
|
||||
|
||||
### Step 3: Decode obvious layers
|
||||
|
||||
Apply base64/rot13/gzinflate where present to reveal the inner command interface (best-effort,
|
||||
non-executing).
|
||||
|
||||
### Step 4: Classify and report
|
||||
|
||||
Note the language, the password/parameter gate (if any), and capabilities (file manager, command
|
||||
exec, SQL, upload); defang IOCs.
|
||||
|
||||
## Validation
|
||||
|
||||
- A flagged file pairs an execution sink with attacker-controlled input.
|
||||
- Encoder chains are identified and, where safe, decoded statically.
|
||||
- Capability classification is backed by concrete code evidence.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Legitimate admin tools that also use `eval`/`system` — corroborate with input flow.
|
||||
- Multi-stage shells that fetch the real logic remotely.
|
||||
- Deeply nested or runtime-built obfuscation that static decode only partly unwinds.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the scanner.
|
||||
- ATT&CK T1505.003 (linked in frontmatter).
|
||||
@@ -0,0 +1,41 @@
|
||||
# API Reference: Analyzing Webshells
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` scans a server-side script (PHP/ASPX/JSP) for dynamic-execution sinks,
|
||||
request-input-to-sink data flow, and encoder chains, and best-effort decodes base64/raw-deflate
|
||||
layers that wrap webshell logic. It reads the file statically, executes nothing, and defangs URLs.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | base64, zlib, regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `scan(path)`
|
||||
Returns language, execution sinks, input sources, an input-to-sink flow flag, encoder chains,
|
||||
decoded layers, defanged URLs, and a webshell likelihood flag.
|
||||
|
||||
### `_decode_layers(text)`
|
||||
Decodes base64 (and raw-deflate) blobs and keeps those whose plaintext contains execution sinks.
|
||||
|
||||
## Detection logic
|
||||
|
||||
- **Sinks**: PHP `eval`/`system`/`passthru`/`preg_replace /e`; ASPX `eval`/`Process.Start`;
|
||||
JSP `Runtime.exec`/`ProcessBuilder`.
|
||||
- **Input flow**: a sink plus `$_GET`/`$_POST`/`Request`/`getParameter` raises confidence.
|
||||
- **Encoders**: `base64_decode`, `gzinflate`, `str_rot13`, `FromBase64String`, `\x` hex.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py scan shell.php
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1505.003 Web Shell — https://attack.mitre.org/techniques/T1505/003/
|
||||
- Server-side execution sink and encoder-chain patterns from public webshell research
|
||||
- PHP/ASP/JSP language execution APIs from their official documentation
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scan a server-side script for webshell indicators and decode obvious obfuscation.
|
||||
|
||||
Detects dynamic-execution sinks, request-input-to-sink flow, and common encoder chains across PHP,
|
||||
ASP/ASPX, and JSP, then best-effort decodes base64/rot13/gzinflate layers. Reads the file
|
||||
statically; executes nothing and never deploys the script. Defangs recovered URLs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
SINKS = {
|
||||
"php": ["eval(", "assert(", "system(", "exec(", "passthru(", "shell_exec(", "popen(",
|
||||
"proc_open(", "preg_replace(/.*/e"],
|
||||
"aspx": ["eval(", "process.start", "executereader", "createobject(\"wscript.shell\")",
|
||||
"scriptengine"],
|
||||
"jsp": ["runtime.getruntime().exec", "processbuilder", "scriptengine", ".eval("],
|
||||
}
|
||||
INPUT_SOURCES = ["$_get", "$_post", "$_request", "$_cookie", "request.form", "request.querystring",
|
||||
"request.getparameter", "request[", "php://input"]
|
||||
ENCODERS = ["base64_decode", "gzinflate", "gzuncompress", "str_rot13", "frombase64string",
|
||||
"eval(chr", "\\x", "hex2bin", "convert.frombase64string"]
|
||||
RE_URL = re.compile(r"https?://[^\s\"'<>)]+", re.I)
|
||||
RE_B64 = re.compile(r"[A-Za-z0-9+/]{40,}={0,2}")
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def _decode_layers(text: str) -> list[str]:
|
||||
out = []
|
||||
for blob in RE_B64.findall(text)[:50]:
|
||||
try:
|
||||
raw = base64.b64decode(blob, validate=True)
|
||||
except (binascii.Error, ValueError):
|
||||
continue
|
||||
candidate = raw
|
||||
try:
|
||||
candidate = zlib.decompress(raw, -15) # gzinflate (raw deflate)
|
||||
except zlib.error:
|
||||
pass
|
||||
printable = candidate.decode("latin-1", "ignore")
|
||||
if any(s in printable.lower() for lst in SINKS.values() for s in lst):
|
||||
out.append(printable[:200])
|
||||
return out[:10]
|
||||
|
||||
|
||||
def scan(path: str) -> dict:
|
||||
text = Path(path).read_text(encoding="utf-8-sig", errors="replace")
|
||||
low = text.lower()
|
||||
lang = ("php" if "<?php" in low or path.endswith(".php")
|
||||
else "aspx" if path.endswith((".aspx", ".asp")) or "<%@" in low
|
||||
else "jsp" if path.endswith(".jsp") or "<%@ page" in low else "unknown")
|
||||
sink_set = SINKS.get(lang, [s for lst in SINKS.values() for s in lst])
|
||||
sinks = sorted({s.rstrip("(") for s in sink_set if s in low})
|
||||
inputs = sorted({i for i in INPUT_SOURCES if i in low})
|
||||
encoders = sorted({e for e in ENCODERS if e in low})
|
||||
urls = sorted({_defang(u) for u in RE_URL.findall(text)})
|
||||
input_reaches_sink = bool(sinks and inputs)
|
||||
return {
|
||||
"language": lang,
|
||||
"execution_sinks": sinks,
|
||||
"input_sources": inputs,
|
||||
"input_to_sink_flow": input_reaches_sink,
|
||||
"encoder_chains": encoders,
|
||||
"decoded_layers": _decode_layers(text),
|
||||
"urls": urls[:50],
|
||||
"is_likely_webshell": bool(sinks and (inputs or encoders)),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze a suspected webshell")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("scan")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(scan(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user