首页 网络安全 正文
  • 本文约8860字,阅读需44分钟
  • 130
  • 0

Woahai321 ListSync SRF漏洞CVE-2026-3958

摘要

栋科技漏洞库关注到 Woahai321 ListSync 在 0.6.6 之前的版本中存在的SSRF漏洞,漏洞追踪为CVE-2026-3958,CVSS 4.0评分5.3。

Woahai321 ListSync 是一个用于列表同步的开源工具,作为影视清单自动化同步中间件,可用于打通观影发现与媒体服务器请求环节。

一、基本情况

Woahai321 ListSync 是一款开源自动化工具,全自动化、多平台兼容、可视化管理、轻量易部署、开源可定制、适配多用户媒体中心。

可将 IMDb、Trakt、Letterboxd 的影视清单自动同步到 Overseerr/Jellyseerr 媒体请求服务器,实现观影清单与家庭媒体中心无缝对接。

Woahai321 ListSync SRF漏洞CVE-2026-3958

栋科技漏洞库关注到 Woahai321 ListSync 在 0.6.6 之前的版本中存在的SSRF漏洞,漏洞追踪为CVE-2026-3958,CVSS 4.0评分5.3。

二、漏洞分析

CVE-2026-3958 源于 api_server.py 文件 JSON Handler 组件中,requests.post 函数在处理用户提供的输入时未进行有效的安全验证。

POST /api/notifications/test 接口在请求体中接收用户提供的 webhook_url,并将其直接传入 requests.post()(或 DiscordWebhook)。

这期间未进行任何 URL 校验或白名单检查,这就导致攻击者可以发送构造的 JSON 数据,将 webhook_url 指向自己控制的服务器。

远程攻击者通过构造恶意 JSON 请求,诱导服务器向内网或第三方服务器发起非预期的 POST 请求,导致服务端请求伪造(SSRF)。

应用会向该 URL 发起出站 HTTP 请求,可通过服务器 IP 产生的 DNS 回调记录确认攻击成功。

此类 SSRF 漏洞可用于内网扫描、窃取云平台元数据(如 AWS IMDSv1)或端口探测,探测内网服务、绕过防火墙或攻击内部应用。

1、来源

// list-sync-main/api_server.py#L6858C1-L6968C99
6858→async def test_discord_notification(payload: dict = None):
6859→    """Send a test Discord notification to verify webhook configuration"""
6860→    try:
6861→        # Get Discord webhook URL from request body or environment
6862→        webhook_url = None
6863→        if payload and 'webhook_url' in payload:
6864→            webhook_url = payload['webhook_url']
6865→        
6866→        if not webhook_url:
6867→            webhook_url = os.getenv('DISCORD_WEBHOOK_URL', '')
6868→        
6869→        if not webhook_url:
6870→            raise HTTPException(
6871→                status_code=400, 
6872→                detail="Discord webhook URL is required. Please provide a webhook URL or set DISCORD_WEBHOOK_URL in your environment variables."
6873→            )
6874→        
6875→        # Try to use the discord-webhook lipary if available
6876→        try:
6877→            from discord_webhook import DiscordWebhook, DiscordEmbed
6878→            from datetime import datetime
6879→            
6880→            # Create webhook instance - explicitly set content to None to avoid duplicate messages
6881→            webhook = DiscordWebhook(url=webhook_url, username="ListSync Test", content=None)
6882→            
6883→            # Create embed with test message
6884→            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
6885→            embed = DiscordEmbed(
6886→                title="🧪 Discord Integration Test",
6887→                description="If you see this message, Discord notifications are working correctly! ✅",
6888→                color=10181046  # Purple color
6889→            )
6890→            
6891→            embed.add_embed_field(
6892→                name="Test Time",
6893→                value=current_time,
6894→                inline=True
6895→            )
6896→            
6897→            embed.add_embed_field(
6898→                name="Status",
6899→                value="✅ Connected",
6900→                inline=True
6901→            )
6902→            
6903→            embed.set_footer(text="ListSync Notification System")
6904→            embed.set_timestamp()
6905→            
6906→            # Add embed to webhook (only embed, no content)
6907→            webhook.add_embed(embed)
6908→            
6909→            # Send webhook
6910→            response = webhook.execute()
6911→            
6912→            return {
6913→                "success": True,
6914→                "message": "Test notification sent successfully! Check your Discord channel.",
6915→                "timestamp": current_time
6916→            }
6917→            
6918→        except ImportError:
6919→            # Fallback to using requests directly
6920→            from datetime import datetime
6921→            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
6922→            
6923→            # Only send embed, no content to avoid duplicate messages
6924→            payload = {
6925→                "embeds": [{
6926→                    "title": "🧪 Discord Integration Test",
6927→                    "description": "If you see this message, Discord notifications are working correctly! ✅",
6928→                    "color": 10181046,
6929→                    "fields": [
6930→                        {
6931→                            "name": "Test Time",
6932→                            "value": current_time,
6933→                            "inline": True
6934→                        },
6935→                        {
6936→                            "name": "Status",
6937→                            "value": "✅ Connected",
6938→                            "inline": True
6939→                        }
6940→                    ],
6941→                    "footer": {
6942→                        "text": "ListSync Notification System"
6943→                    },
6944→                    "timestamp": datetime.utcnow().isoformat()
6945→                }]
6946→            }
6947→            
6948→            response = requests.post(webhook_url, json=payload, timeout=10)
6949→            response.raise_for_status()
6950→            
6951→            return {
6952→                "success": True,
6953→                "message": "Test notification sent successfully! Check your Discord channel.",
6954→                "timestamp": current_time
6955→            }
6956→        
6957→    except requests.exceptions.Timeout:
6958→        raise HTTPException(status_code=504, detail="Discord webhook request timed out")
6959→    except requests.exceptions.RequestException as e:
6960→        error_msg = f"Failed to send Discord notification: {str(e)}"
6961→        if hasattr(e, 'response') and e.response is not None:
6962→            error_msg += f" (Status: {e.response.status_code})"
6963→        raise HTTPException(status_code=500, detail=error_msg)
6964→    except Exception as e:
6965→        import traceback
6966→        error_detail = f"Failed to send test notification: {str(e)}\n{traceback.format_exc()}"
6967→        logging.error(error_detail)
6968→        raise HTTPException(status_code=500, detail=f"Failed to send test notification: {str(e)}")

 

2、接收点

// list-sync-main/api_server.py#L6858C1-L6968C99
6858→async def test_discord_notification(payload: dict = None):
6859→    """Send a test Discord notification to verify webhook configuration"""
6860→    try:
6861→        # Get Discord webhook URL from request body or environment
6862→        webhook_url = None
6863→        if payload and 'webhook_url' in payload:
6864→            webhook_url = payload['webhook_url']
6865→        
6866→        if not webhook_url:
6867→            webhook_url = os.getenv('DISCORD_WEBHOOK_URL', '')
6868→        
6869→        if not webhook_url:
6870→            raise HTTPException(
6871→                status_code=400, 
6872→                detail="Discord webhook URL is required. Please provide a webhook URL or set DISCORD_WEBHOOK_URL in your environment variables."
6873→            )
6874→        
6875→        # Try to use the discord-webhook lipary if available
6876→        try:
6877→            from discord_webhook import DiscordWebhook, DiscordEmbed
6878→            from datetime import datetime
6879→            
6880→            # Create webhook instance - explicitly set content to None to avoid duplicate messages
6881→            webhook = DiscordWebhook(url=webhook_url, username="ListSync Test", content=None)
6882→            
6883→            # Create embed with test message
6884→            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
6885→            embed = DiscordEmbed(
6886→                title="🧪 Discord Integration Test",
6887→                description="If you see this message, Discord notifications are working correctly! ✅",
6888→                color=10181046  # Purple color
6889→            )
6890→            
6891→            embed.add_embed_field(
6892→                name="Test Time",
6893→                value=current_time,
6894→                inline=True
6895→            )
6896→            
6897→            embed.add_embed_field(
6898→                name="Status",
6899→                value="✅ Connected",
6900→                inline=True
6901→            )
6902→            
6903→            embed.set_footer(text="ListSync Notification System")
6904→            embed.set_timestamp()
6905→            
6906→            # Add embed to webhook (only embed, no content)
6907→            webhook.add_embed(embed)
6908→            
6909→            # Send webhook
6910→            response = webhook.execute()
6911→            
6912→            return {
6913→                "success": True,
6914→                "message": "Test notification sent successfully! Check your Discord channel.",
6915→                "timestamp": current_time
6916→            }
6917→            
6918→        except ImportError:
6919→            # Fallback to using requests directly
6920→            from datetime import datetime
6921→            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
6922→            
6923→            # Only send embed, no content to avoid duplicate messages
6924→            payload = {
6925→                "embeds": [{
6926→                    "title": "🧪 Discord Integration Test",
6927→                    "description": "If you see this message, Discord notifications are working correctly! ✅",
6928→                    "color": 10181046,
6929→                    "fields": [
6930→                        {
6931→                            "name": "Test Time",
6932→                            "value": current_time,
6933→                            "inline": True
6934→                        },
6935→                        {
6936→                            "name": "Status",
6937→                            "value": "✅ Connected",
6938→                            "inline": True
6939→                        }
6940→                    ],
6941→                    "footer": {
6942→                        "text": "ListSync Notification System"
6943→                    },
6944→                    "timestamp": datetime.utcnow().isoformat()
6945→                }]
6946→            }
6947→            
6948→            response = requests.post(webhook_url, json=payload, timeout=10)
6949→            response.raise_for_status()
6950→            
6951→            return {
6952→                "success": True,
6953→                "message": "Test notification sent successfully! Check your Discord channel.",
6954→                "timestamp": current_time
6955→            }
6956→        
6957→    except requests.exceptions.Timeout:
6958→        raise HTTPException(status_code=504, detail="Discord webhook request timed out")
6959→    except requests.exceptions.RequestException as e:
6960→        error_msg = f"Failed to send Discord notification: {str(e)}"
6961→        if hasattr(e, 'response') and e.response is not None:
6962→            error_msg += f" (Status: {e.response.status_code})"
6963→        raise HTTPException(status_code=500, detail=error_msg)
6964→    except Exception as e:
6965→        import traceback
6966→        error_detail = f"Failed to send test notification: {str(e)}\n{traceback.format_exc()}"
6967→        logging.error(error_detail)
6968→        raise HTTPException(status_code=500, detail=f"Failed to send test notification: {str(e)}")

三、POC概念验证

1、POC代码

管理员已设置登录后刷新可查看

2、输出结果

Sandbox Execution Cancelled
++++++++++++++++++++++++++++++++++++ Dnslog ++++++++++++++++++++++++++++++++++++
Request was made from IP: 172.217.46.16, 69.28.61.220, 69.28.61.221
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

3、预期行为

仅允许访问受信任的 URL

四、影响范围

Woahai321 ListSync <= 0.6.6

五、修复建议

Woahai321 ListSync > 0.6.6

六、参考链接

管理员已设置登录后刷新可查看



扫描二维码,在手机上阅读
评论
更换验证码
友情链接