Skip to content

Commit

Permalink
introduce mitigation for Server Side Request Forgery in tracker annou…
Browse files Browse the repository at this point in the history
…nces
  • Loading branch information
arvidn committed Nov 23, 2020
1 parent a9c406c commit 172321f
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 0 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* introduce mitigation for Server Side Request Forgery in tracker announces

1.2.11 released

Expand Down
2 changes: 2 additions & 0 deletions include/libtorrent/http_connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ struct TORRENT_EXTRA_EXPORT http_connection

std::vector<tcp::endpoint> const& endpoints() const { return m_endpoints; }

std::string const& url() const { return m_url; }

private:

#if TORRENT_USE_I2P
Expand Down
6 changes: 6 additions & 0 deletions include/libtorrent/settings_pack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,12 @@ namespace aux {
// HTTPS trackers to fail.
validate_https_trackers,

// when enabled, any HTTP(S) tracker requests to localhost (loopback)
// must have the request path start with "/announce". This is the
// conventional bittorrent tracker request. Any other HTTP(S)
// tracker request to loopback will be ignored.
tracker_ssrf_mitigation,

max_bool_setting_internal
};

Expand Down
61 changes: 61 additions & 0 deletions simulation/test_tracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,13 @@ struct sim_config : sim::default_config
result.push_back(address_v6::from_string("ff::dead:beef"));
return duration_cast<chrono::high_resolution_clock::duration>(chrono::milliseconds(100));
}
if (hostname == "localhost")
{
result.push_back(address_v4::from_string("127.0.0.1"));
if (ipv6)
result.push_back(address_v6::from_string("::1"));
return duration_cast<chrono::high_resolution_clock::duration>(chrono::milliseconds(1));
}

return default_config::hostname_lookup(requestor, hostname, result, ec);
}
Expand Down Expand Up @@ -607,17 +614,25 @@ void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2
sim::asio::io_service tracker_ios(sim, address_v4::from_string("123.0.0.2"));
sim::asio::io_service tracker_ios6(sim, address_v6::from_string("ff::dead:beef"));

sim::asio::io_service tracker_lo_ios(sim, address_v4::from_string("127.0.0.1"));
sim::asio::io_service tracker_lo_ios6(sim, address_v6::from_string("::1"));

// listen on port 8080
sim::http_server http(tracker_ios, 8080);
sim::http_server http6(tracker_ios6, 8080);
sim::http_server http_lo(tracker_lo_ios, 8080);
sim::http_server http6_lo(tracker_lo_ios6, 8080);

http.register_handler(url_path, a);
http6.register_handler(url_path, a);
http_lo.register_handler(url_path, a);
http6_lo.register_handler(url_path, a);

lt::session_proxy zombie;

asio::io_service ios(sim, { address_v4::from_string("123.0.0.3")
, address_v6::from_string("ffff::1337") });

lt::settings_pack sett = settings();
std::unique_ptr<lt::session> ses(new lt::session(sett, ios));

Expand Down Expand Up @@ -1319,6 +1334,52 @@ TORRENT_TEST(tracker_user_agent_privacy_mode_private_torrent)
TEST_EQUAL(got_announce, true);
}

void test_ssrf(char const* announce_path, bool const feature_on
, char const* tracker_url, bool const expect_announce)
{
bool got_announce = false;
tracker_test(
[&](lt::add_torrent_params& p, lt::session& ses)
{
settings_pack pack;
pack.set_bool(settings_pack::tracker_ssrf_mitigation, feature_on);
ses.apply_settings(pack);
p.trackers.emplace_back(tracker_url);
return 60;
},
[&](std::string method, std::string req
, std::map<std::string, std::string>& headers)
{
got_announce = true;
return sim::send_response(200, "OK", 11) + "d5:peers0:e";
}
, [](torrent_handle h) {}
, [](torrent_handle h) {}
, announce_path);
TEST_EQUAL(got_announce, expect_announce);
}

TORRENT_TEST(tracker_ssrf_localhost)
{
test_ssrf("/announce", true, "http://localhost:8080/announce", true);
test_ssrf("/unusual-announce-path", true, "http://localhost:8080/unusual-announce-path", false);
test_ssrf("/unusual-announce-path", false, "http://localhost:8080/unusual-announce-path", true);
}

TORRENT_TEST(tracker_ssrf_IPv4)
{
test_ssrf("/announce", true, "http://127.0.0.1:8080/announce", true);
test_ssrf("/unusual-announce-path", true, "http://127.0.0.1:8080/unusual-announce-path", false);
test_ssrf("/unusual-announce-path", false, "http://127.0.0.1:8080/unusual-announce-path", true);
}

TORRENT_TEST(tracker_ssrf_IPv6)
{
test_ssrf("/announce", true, "http://[::1]:8080/announce", true);
test_ssrf("/unusual-announce-path", true, "http://[::1]:8080/unusual-announce-path", false);
test_ssrf("/unusual-announce-path", false, "http://[::1]:8080/unusual-announce-path", true);
}

// This test sets up two peers, one seed an one downloader. The downloader has
// two trackers, both in tier 0. The behavior we expect is that it picks one of
// the trackers at random and announces to it. Since both trackers are working,
Expand Down
41 changes: 41 additions & 0 deletions src/http_tracker_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/aux_/session_settings.hpp"
#include "libtorrent/resolver_interface.hpp"
#include "libtorrent/ip_filter.hpp"
#include "libtorrent/parse_url.hpp"

namespace libtorrent {

Expand Down Expand Up @@ -286,6 +287,46 @@ namespace libtorrent {
return;
}

aux::session_settings const& settings = m_man.settings();
bool const ssrf_mitigation = settings.get_bool(settings_pack::tracker_ssrf_mitigation);
if (ssrf_mitigation && std::find_if(endpoints.begin(), endpoints.end()
, [](tcp::endpoint const& ep) { return is_loopback(ep.address()); }) != endpoints.end())
{
// there is at least one loopback address in here. If the request
// path for this tracker is not /announce. filter all loopback
// addresses.
std::string path;

error_code ec;
std::tie(std::ignore, std::ignore, std::ignore, std::ignore, path)
= parse_url_components(c.url(), ec);
if (ec)
{
fail(ec);
return;
}

// this is mitigation for Server Side request forgery. Any tracker
// announce to localhost need to look like a standard BitTorrent
// announce
if (path.substr(0, 9) != "/announce")
{
for (auto i = endpoints.begin(); i != endpoints.end();)
{
if (is_loopback(i->address()))
i = endpoints.erase(i);
else
++i;
}
}

if (endpoints.empty())
{
fail(errors::banned_by_ip_filter);
return;
}
}

TORRENT_UNUSED(c);
if (!tracker_req().filter) return;

Expand Down
1 change: 1 addition & 0 deletions src/settings_pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
SET(dht_prefer_verified_node_ids, true, &session_impl::update_dht_settings),
SET(piece_extent_affinity, false, nullptr),
SET(validate_https_trackers, false, &session_impl::update_validate_https),
SET(tracker_ssrf_mitigation, true, nullptr),
}});

aux::array<int_setting_entry_t, settings_pack::num_int_settings> const int_settings
Expand Down

0 comments on commit 172321f

Please sign in to comment.