5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / server.c C
#define _GNU_SOURCE
/*
 * server.c - Minimal HTTP server that transcodes POST bodies via iconv.
 *
 * Listens on port 8080. Accepts POST requests with a charset parameter
 * in Content-Type and converts the body to UTF-8 using iconv().
 *
 * This simulates a realistic attack surface: any web service on Alpine
 * Linux that processes user-supplied text in non-UTF-8 encodings.
 *
 * Usage: ./server
 * Test:  curl -X POST -H "Content-Type: text/plain; charset=gb18030" \
 *             --data-binary @payload.bin http://localhost:8080/
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <iconv.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define MAX_REQ 65536

static double now(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec * 1e-9;
}

/* Extract charset from Content-Type header, return "UTF-8" if not found */
static const char *extract_charset(const char *headers)
{
    static char charset[64];
    const char *p = strcasestr(headers, "charset=");
    if (!p) return NULL;
    p += 8;
    int i = 0;
    while (*p && *p != '\r' && *p != '\n' && *p != ';' && *p != ' '
           && i < (int)sizeof(charset)-1)
        charset[i++] = *p++;
    charset[i] = 0;
    return charset;
}

/* Extract Content-Length */
static int extract_content_length(const char *headers)
{
    const char *p = strcasestr(headers, "content-length:");
    if (!p) return 0;
    p += 15;
    while (*p == ' ') p++;
    return atoi(p);
}

/* Transcode body from `from_charset` to UTF-8 using iconv */
static int transcode(const char *from_charset, const char *body, size_t bodylen,
                     char *out, size_t outlen, double *elapsed)
{
    iconv_t cd = iconv_open("UTF-8", from_charset);
    if (cd == (iconv_t)-1) {
        snprintf(out, outlen, "iconv_open failed for charset '%s': %s",
                 from_charset, strerror(errno));
        return 400;
    }

    char *inp = (char *)body;
    size_t inleft = bodylen;
    char *outp = out;
    size_t outleft = outlen - 1;

    double t0 = now();
    size_t ret = iconv(cd, &inp, &inleft, &outp, &outleft);
    *elapsed = now() - t0;

    iconv_close(cd);

    if (ret == (size_t)-1) {
        snprintf(out, outlen, "iconv failed: %s (consumed %zu/%zu bytes in %.3fs)",
                 strerror(errno), bodylen - inleft, bodylen, *elapsed);
        return 400;
    }

    *outp = 0;
    return 200;
}

static void handle_client(int client_fd)
{
    char req[MAX_REQ];
    int total = 0;
    int header_end = 0;

    /* Read the full request (headers + body) */
    while (total < MAX_REQ - 1) {
        int n = read(client_fd, req + total, MAX_REQ - 1 - total);
        if (n <= 0) break;
        total += n;
        req[total] = 0;

        /* Check if we've received the full headers */
        if (!header_end) {
            char *hend = strstr(req, "\r\n\r\n");
            if (hend) {
                header_end = (hend - req) + 4;
                int clen = extract_content_length(req);
                /* If we have all the body, stop reading */
                if (total >= header_end + clen)
                    break;
            }
        }
    }

    if (!header_end) {
        const char *resp = "HTTP/1.0 400 Bad Request\r\n\r\nMalformed request\n";
        write(client_fd, resp, strlen(resp));
        close(client_fd);
        return;
    }

    const char *charset = extract_charset(req);
    int clen = extract_content_length(req);
    const char *body = req + header_end;
    int bodylen = total - header_end;
    if (bodylen > clen) bodylen = clen;

    char response_body[MAX_REQ];
    double elapsed = 0;

    if (!charset || bodylen == 0) {
        snprintf(response_body, sizeof(response_body),
                 "Send POST with Content-Type: text/plain; charset=gb18030\n"
                 "Body: the text to transcode\n");
    } else {
        printf("[request] charset=%s bodylen=%d ... ", charset, bodylen);
        fflush(stdout);

        int status = transcode(charset, body, bodylen,
                              response_body, sizeof(response_body), &elapsed);

        printf("done in %.3fs (status %d)\n", elapsed, status);
        fflush(stdout);
    }

    char response[MAX_REQ + 256];
    int rlen = snprintf(response, sizeof(response),
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: text/plain\r\n"
        "X-Transcode-Time: %.6f\r\n"
        "\r\n"
        "Transcode time: %.6f seconds\n"
        "Input: %d bytes (%s)\n"
        "Output:\n%s\n",
        elapsed, elapsed,
        bodylen, charset ? charset : "none",
        response_body);

    write(client_fd, response, rlen);
    close(client_fd);
}

int main(void)
{
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) { perror("socket"); return 1; }

    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = INADDR_ANY
    };

    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind"); return 1;
    }
    if (listen(server_fd, 5) < 0) {
        perror("listen"); return 1;
    }

    printf("Listening on port %d (musl iconv transcode server)\n", PORT);
    printf("Send requests like:\n");
    printf("  curl -X POST -H 'Content-Type: text/plain; charset=gb18030' "
           "--data-binary @payload.bin http://localhost:%d/\n\n", PORT);
    fflush(stdout);

    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) { perror("accept"); continue; }
        handle_client(client_fd);
    }
}