How to Download Files in Linux Containers Without Curl or Wget: A Guide for Alpine Linux

How to Implement Download Functionality in Linux Without curl and wget Using Shell

Previously, we analyzed how to download files in Linux without using curl and wget by using exec to specify /dev/tcp/ip/port. However, in container scenarios, there are no pseudo-device files like /dev/tcp. So, how can we achieve remote downloading for a Linux file download?

This article uses the Alpine image as an example. Alpine, being the most lightweight container image, is only about 5MB. This means many common commands, including curl and wget, are not available, and the container does not have the /dev/tcp device, making the exec trick unusable.

Scenario Analysis

In offensive and defensive scenarios, we usually gain access to the container, possibly through command execution or a reverse shell. However, the container lacks any tools for downloading, and we cannot install commands via apk.

How to Determine the Base Image of a Container?

 cat /etc/os-release
Linux file download

/etc/os-release can be used to determine the base image of the container.

Countermeasure Strategy

Since we cannot install download command tools within the container, we can manually write a pre-compiled download tool into the container.

Here, I implemented a simple file download tool in C that only supports the HTTP protocol.

 #include <arpa/inet.h> 
#include  
#include  
#include <netinet/in.h> 
#include  
#include  
#include  
#include <sys/socket.h> 
#include <sys/types.h> 
#include  

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, " C Downloader \n\n Usage: %s  \n only support http protocol \n", argv[0]);
        return 1;
    }

    char *url = argv[1];
    char *host_start = strstr(url, "http://");
    if (host_start) {
        host_start += 7;
    } else {
        host_start = url;
    }

    char *host = strtok(host_start, "/");
    char *port_str = "80";
    char *path = strtok(NULL, "");
    
    char *colon = strchr(host, ':');
    if (colon) {
        *colon = '\0';
        port_str = colon + 1;
    }

    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char request[256];
    char buffer[1024];
    int file;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(host, port_str, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    for (p = servinfo; p != NULL; p = p-> ai_next) {
        if ((sockfd = socket(p-> ai_family, p-> ai_socktype, p-> ai_protocol)) == -1) {
            perror("socket");
            continue;
        }

        if (connect(sockfd, p-> ai_addr, p-> ai_addrlen) == -1) {
            close(sockfd);
            perror("connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "failed to connect\n");
        return 2;
    }

    freeaddrinfo(servinfo);

    snprintf(request, sizeof(request), "GET /%s HTTP/1.1\r\nHost: %s:%s\r\nConnection: close\r\n\r\n", path, host, port_str);
    send(sockfd, request, strlen(request), 0);

    file = open("downloaded_file", O_WRONLY | O_CREAT | O_TRUNC, 0644);

    int received;
    int header_end = 0;
    while ((received = recv(sockfd, buffer, sizeof(buffer), 0)) >  0) {
        if (!header_end) {
            char *header_end_ptr = strstr(buffer, "\r\n\r\n");
            if (header_end_ptr) {
                header_end = 1;
                write(file, header_end_ptr + 4, received - (header_end_ptr - buffer) - 4);
            }
        } else {
            write(file, buffer, received);
        }
    }

    close(file);
    close(sockfd);

return 0;
}

Pitfalls of Compiling the Download Tool

We need to compile the above C code, but we must note that the dynamic link libraries on the host and in the Alpine container are different.

Here, we use gcc to compile the same C code.

 gcc -Os -s -fdata-sections -ffunction-sections -flto -Wl,--gc-sections -o downloader downloader.c
Linux file download

Therefore, programs compiled on the host cannot run in the container and need to be recompiled within the container!

Since Alpine Linux uses musl libc as the default C library, programs compiled in the Alpine environment will automatically link to musl libc.

To compile a binary that depends on musl libc on Alpine Linux, you need to use an Alpine Linux environment and ensure the necessary compilation tools are installed.

Compilation Process

 docker run --name alpine -dit alpine
// Enter the container
docker exec -it alpine sh
// Install gcc
apk add --no-cache build-base
// Compile the tool code
gcc -Os -s -fdata-sections -ffunction-sections -flto -Wl,--gc-sections -o downloader downloader.c

gzip Compression of Shellcode

The compiled binary file is usually several kilobytes, which, although not large, can still affect our transmission. We can use gzip to compress it to the smallest size.

After gzip compression, the length is acceptable. Here, I have placed the compressed shellcode below.

 / # cat output | base64 -d > new.gz 
/ # gzip -d new.gz 
/ # chmod +x new
/ # ./new
 C Downloader 

 Usage: ./new  
 only support http protocol 

Supplement

Some Alpine images actually come with busybox. If you have it, there’s no need to go through all the trouble. You can directly use curl or wget from busybox to download the file.

Post Views: 814