Search code examples
c++boostboost-asio

Sanitize a hostname using Boost C++


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;
}

Solution

  • Like others said, don't sanitize, just don't unsafely pass arguments. Your ping tool will already sanitize, and you never invoked a shell.

    Live On Coliru

    #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: