CVE-2024-45216 Authentication bypass in Apache Solr

gumgumgumgum
5 min read

Apache Solr là một nền tảng tìm kiếm mã nguồn mở dựa trên Apache Lucene, cung cấp các công cụ mạnh mẽ để xây dựng các ứng dụng tìm kiếm và phân tích dữ liệu. Được thiết kế để xử lý khối lượng dữ liệu lớn với tốc độ cao, Solr được sử dụng rộng rãi trong các hệ thống tìm kiếm, quản lý nội dung, phân tích và các ứng dụng yêu cầu xử lý ngôn ngữ tự nhiên.

Tổng quan

Các phiên bản Solr từ 5.3.0 tới 8.11.4 hoặc 9.7.0 trở xuống sử dụng PKIAuthenticationPlugin (được kích hoạt mặc định khi bật xác thực) có lỗ hổng cho phép bỏ qua xác thực bằng cách thêm một đoạn giả mạo vào cuối đường dẫn URL của bất kỳ API nào trong Solr.

Setup

Solr cấu hình xác thực thông qua file security.json.

{
  "authentication": {
    "class": "solr.BasicAuthPlugin",
    "credentials": {
      "solr": "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c="
    },
    "blockUnknown": false,
    "": {
      "v": 0
    }
  },
  "authorization": {
    "class": "solr.RuleBasedAuthorizationPlugin",
    "permissions": [
      {
        "name": "security-edit",
        "role": "admin"
      }
    ],
    "user-role": {
      "solr": "admin"
    }
  }
}
  • Bật plugin BasicAuthPlugin và RuleBasedAuthoricationPlugin cho authen và author.

  • Tạo một user với username là “solr” và password là “SolrRocks“.

  • Tạo một role “admin” có quyền “security-edit” và gán cho user “solr”.

Vì lỗ hổng này nằm ở plugin PKIAuthentication (chỉ khả dụng ở chế độ SolrCloud) nên mình sẽ dựng bằng docker-compose.yaml cho tiện và cấu hình auth bằng file security.json ở trên.

version: '3.7'
services:
  solr1:
    image: solr:9.6.0
    container_name: solr1e
    ports:
     - "8983:8983"
     - "5006:5006"
    environment:
      - ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
      - SOLR_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5006
    volumes:
      - [your-local-path]security.json:/var/solr/data/security.json
    networks:
      - solr
    depends_on:
      - zoo1
      - zoo2
      - zoo3


  solr2:
    image: solr:9.6.0
    container_name: solr2
    ports:
     - "8982:8983"
    environment:
      - ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
    networks:
      - solr
    depends_on:
      - zoo1
      - zoo2
      - zoo3


  solr3:
    image: solr:9.6.0
    container_name: solr3
    ports:
     - "8981:8983"
    environment:
      - ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
    networks:
      - solr
    depends_on:
      - zoo1
      - zoo2
      - zoo3


  zoo1:
    image: zookeeper:3.8
    container_name: zoo1
    restart: always
    hostname: zoo1
    ports:
      - 2181:2181
      - 7001:7000
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
      ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
      ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
    networks:
      - solr

  zoo2:
    image: zookeeper:3.8
    container_name: zoo2
    restart: always
    hostname: zoo2
    ports:
      - 2182:2181
      - 7002:7000
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
      ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
      ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
    networks:
      - solr

  zoo3:
    image: zookeeper:3.8
    container_name: zoo3
    restart: always
    hostname: zoo3
    ports:
      - 2183:2181
      - 7003:7000
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
      ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
      ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
    networks:
      - solr

networks:
  solr:

Chạy lệnh dưới trên solr để add security.json vào Zookeeper

solr zk cp /var/solr/data/security.json zk:/security.json -z zoo1:2181,zoo2:2181,zoo3:2181

Sau đó đăng nhập vào Solr bằng tài khoản ở trên. Bật option “Block anonymous request”

Phân tích

Ở đây mình sử dụng version lỗi là 9.6.0 diff với bản 9.7.0.

Trong mô tả của NVD thì lỗ hổng nằm ở class PKIAuthenticationPlugin nên mình sẽ diff hai file này trước.

Nói qua một chút về PKIAuthenticationPlugin
PKIAuthenticationPlugin là một plugin xác thực trong chế độ SolrCloud thay thế cho Basic authentication, được sử dụng khi có request giữa hai node Solr. Khi có request gửi đi, PKIAuthenticationPlugin sẽ thêm một header có tên là SolrAuth. Header này chứa timestamp và principal được mã hóa bằng khóa riêng của node gửi. Mỗi node sẽ cung cấp khóa công khai qua một API, để các node khác có thể truy cập khi cần. Khi một node nhận yêu cầu kèm theo header SolrAuth, nó sẽ lấy khóa công khai từ node gửi để giải mã thông tin.

Tại method doAuthenticate ta thấy được tại bản vá đã bị xóa đi đoạn kiểm tra uri từ request.

method này kiểm tra uri request nhận được có kết thúc bằng /admin/info/key không, nếu bằng thì sẽ forward request tới filter chain tiếp theo rồi cuối cùng return true, bỏ qua hoàn toàn các đoạn xác thực tiếp theo bên dưới.

Đặt debug ở dòng 145 của file PKIAuthenticationPlugin để xem chi tiết hơn code flow đoạn này.

Send request như trên lúc này ta thấy không trigger được breakpoint thì nguyên nhân của điều này là tại method SolrDispatchFilter.authenticateRequest() có kiểm tra requestPath tức đoạn uri sau /solr nếu bằng /admin/info/key sẽ return ngay khiến flow không tới được PKIAuthenticationPlugin.doAuthenticate().

Đây cũng chính là unauth endpoint để trả về public key của một node mà mình đã nói ở trên.

Send lại một request khác

lúc này đã vào được method doAuthenticate() và bypass được đoạn check public key header ở dưới.

Như vậy ta có thể vượt qua method doAuthenticate() này mà không cần có một header SolrAuth hợp lệ bằng cách tạo một request có uri kết thúc bằng /admin/info/key. Có vẻ đây chính là “fake ending” được nhắc tới trong mô tả của NVD.

Giờ ta đã biết được cách có thể bypass được đoạn xác thực request rồi nhưng làm sao để ta có thể truy cập vào một api bất kỳ nào khác ngoài cái /admin/info/key. Trong mô tả lỗi nói rằng trước khi thực hiện API routing thì đoạn “fake ending” sẽ bị loại bỏ. Chắc chắn đằng sau có một hàm có thể dùng để xóa đi đoạn “fake ending” này. Tiếp tục F7+F8

Sau khi qua bước check authen ở trên thì tại line 262, SolrDispatchFilter tiếp tục gọi tới method HttpSolrCall.call()HttpSolrCall.init().

có thể thấy rõ rằng biến path tức request path của ta được xóa đi từ kí tự “:” đầu tiên. Sau khi kiểm tra lại file diff thì ở bản vá cũng đã không còn đoạn substring này.

Send lại request dưới (có “:”)

đoạn sau “:” đã bị xóa

Có vẻ như đây chính là mảnh ghép cuối của lỗ hổng này. Bây giờ ta có thể access vào bất kì API nào trên solr mà không cần phải xác thực.

0
Subscribe to my newsletter

Read articles from gumgum directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

gumgum
gumgum