I'm trying to pass a hostname/ipv4/ipv6 to the ping utility in linux (to avoid using raw sockets, root), however, I want to ensure it's valid before running the command. For example, if a malicious domain was supplied, say
char host[] = "google.com;cat > pwned.txt";
the sanitizer should reject it because it would allow remote code execution
ping -c 4 google.com;cat > pwned.txt
So far, I've coded this below to detect a valid ipv4, ipv6 and hostname.
Question
The code smells imo, so I'd like to know if there's a better Boost C++ approach to sanitizing the user input?
#include <iostream>
#include <string>
#include <algorithm>
#include <boost/asio.hpp>
int main(int argc, char* argv[])
{
//char host[] = "google.com";
char host[] = "google.com;cat > pwned.txt";
std::string allowed = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-.";
bool bValid = false;
boost::asio::ip::address ipv4Addr;
boost::asio::ip::address ipv6Addr;
try
{
ipv4Addr = boost::asio::ip::address::from_string(host);
bValid = ipv4Addr.is_v4();
}
catch (std::exception& e)
{
std::cerr << "NOT ipv4: " << e.what() << std::endl;
bValid = false;
}
if (!bValid)
{
// not ipv4, let's try ipv6
try
{
ipv6Addr = boost::asio::ip::address::from_string(host);
bValid = ipv6Addr.is_v6();
}
catch (std::exception& e)
{
std::cerr << "NOT ipv6: " << e.what() << std::endl;
bValid = false;
}
if (!bValid)
{
// ipv4 and ipv6 failed, try a string
std::string hostname = std::string(host);
if (hostname.find_first_not_of(allowed) != std::string::npos) {
std::cerr << "NOT valid host" << std::endl;
return 1;
}
}
}
std::string sCommand = "ping -c 4 ";
sCommand.append(host);
std::cout << "Running PING command: " << sCommand << std::endl;
int result = std::system(sCommand.c_str());
if (result != 0) {
std::cerr << "Command failed: " << result << std::endl;
}
return 0;
}
Like others said, don't sanitize, just don't unsafely pass arguments. Your ping tool will already sanitize, and you never invoked a shell.
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
int main(int argc, char** argv) {
if (argc > 1) {
std::error_code ec;
int code = bp::system(bp::search_path("ping"), "-c", "4", "--", argv[1], ec);
std::cout << code << " (" << ec.message() << ")\n";
}
}
Printing e.g.
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_filesystem && ./a.out 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=1.83 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=1.55 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=1.59 ms
64 bytes from 1.1.1.1: icmp_seq=4 ttl=57 time=1.51 ms
--- 1.1.1.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 1.516/1.626/1.834/0.126 ms
0 (Success)
In case you really need to parse the address anyways: