T09 · Insecure Skill Coding Practices
- Location
scripts/send_image.py:41- Finding
Non-Image RIFF Files Bypass Image-Type Validation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/send_image.py, lines 41–53
Vulnerability Type: Improper file-type validation
Risk Level: LowVulnerable Code
python _ALLOWED_MAGIC = { b'\xff\xd8\xff': 'jpeg', b'\x89PNG\r\n\x1a\n': 'png', b'GIF87a': 'gif', b'GIF89a': 'gif', b'RIFF': 'webp', # RIFF....WEBP b'BM': 'bmp', } def _validate_image(path: str) -> bool: """通过 magic bytes 校验是否为图片,返回 True 表示通过""" try: with open(path, 'rb') as f: header = f.read(12) for magic in _ALLOWED_MAGIC: if header.startswith(magic): return True # WebP 特殊判断(RIFF....WEBP) if header[:4] == b'RIFF' and header[8:12] == b'WEBP': return True return False except Exception: return FalseTechnical Analysis
The generic
b'RIFF'signature is accepted by the loop before the code reaches the format-specific WebP check. RIFF is a container format used by WebP as well as non-image formats such as WAV and AVI. Consequently, any file beginning with a valid-lookingRIFFprefix passes the advertised image-only validation even when bytes 8–11 do not containWEBP.After validation, the accepted file is opened and transmitted to Feishu by
upload_image(). It is always assigned the MIME typeimage/jpeg, regardless of the detected format. This mismatch further weakens server-side content handling and does not correct the defective local validation.The network transfer itself is necessary for the Skill’s declared image-sending functionality and uses Feishu’s official HTTPS API. The flaw is that the local restriction intended to limit that transfer to supported image formats is bypassable.
Attack Path
- An attacker or user able to control the command-line image path selects a non-image RIFF file, such as a WAV or AVI file.
- The script reads its first 12 bytes in `_v ...[truncated 1147 chars]
- Remediation
View remediation
Remediation Suggestions
- Remove the generic
b'RIFF'entry from_ALLOWED_MAGIC. - Accept a RIFF file as WebP only when both conditions hold:
- Bytes 0–3 equal
RIFF. - Bytes 8–11 equal
WEBP.
- Bytes 0–3 equal
- Return the verified format from the validation function instead of only a Boolean result.
- Assign the upload MIME type according to that verified format rather than always using
image/jpeg. - Consider decoding the file with a maintained image library and rejecting content that cannot be parsed as a supported image.
- Enforce a maximum file size before upload to reduce accidental disclosure and resource-consumption risks.
- Reject ambiguous or malformed files rather than accepting them based on a short shared container signature.
A minimal correction would be:
python _ALLOWED_MAGIC = { b'\xff\xd8\xff': 'jpeg', b'\x89PNG\r\n\x1a\n': 'png', b'GIF87a': 'gif', b'GIF89a': 'gif', b'BM': 'bmp', } def _validate_image(path: str) -> bool: try: with open(path, 'rb') as f: header = f.read(12) if header[:4] == b'RIFF': return header[8:12] == b'WEBP' return any(header.startswith(magic) for magic in _ALLOWED_MAGIC) except (OSError, PermissionError): return False- Remove the generic
