What's new

Entware Guide to Adding 2FA to Incoming Ports on AX86U

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

MissingTwins

Regular Contributor
I'm currently using RDP and providing a few public services for my family. Since RDP doesn't support public-private key authentication, my open ports have been subjected to extensive scanning. It seems like there's always someone on the internet trying to exploit my ports.

To combat this, I've decided to take action.

I've made a script based on 2FA. This script creates a simple WebUI for authentication and then adds the authenticated IP to IPSet, and inster RDP chain at top of iptabes/ip6tabes FORWARD chain, allowing only registered IPs access.

The script is built on Bash (which can be converted to Shell later), socat, OpenSSL, iptables, ip6tables, IPSet, xxd, base32 (this can be replaced with a script), and sleep(less than 1s). The 'date' command is also used, but it's optional.

The WebUI automatically detects your public IPv4 and IPv6 addresses and sends them to the backend to be added to IPSet.
1701141334184.png

This project comprises five files:
- 1. port_guard.sh: The main script.
- 2. 2fa.sh: A modified version of a 2FA library, credit to 2FA-HOTP-TOTP-Bash.
- 3. opt.sh: Options for post-authentication processes.
- 4. index.html: The WebUI for you to input 2fa code.
- 5. secret_key.txt: Your private key.(Empty YOU MUST GENERATE YOURS) This can be used with 1Password, Google Authenticator, or Microsoft Authenticator.

Setup environment and dependencies
`opkg update && opkg install bash`
`nano /jffs/configs/profile.add`
[ -f /opt/bin/bash ] && exec /opt/bin/bash
`opkg install xxd coreutils-base32 coreutils-sleep coreutils-date`


You can generate the private key using the command:
`source ./2fa.sh; keygen | tee secret_key.txt`
RD76DLH7WBOGF56JHZALFHEMXQ2JRV5Y

Set permission
`chmod +x .port_guard.sh`
The script can be launched as
`socat -d TCP4-LISTEN:65432,reuseaddr,fork,bind=192.168.77.1 SYSTEM:/opt/ect/rdp/port_guard.sh,pipes`
And you can access the WebUI by
`http://192.168.77.1:65432`
It is better to put the WebUI behind nginx proxy so you can utilize https or cloudflare protection.

Here is the main source codes: (Due to the limitation I put all the scripts on Github)
Bash:
#!/opt/bin/bash

echoerr() { echo "$@" 1>&2; }

LF=$'\n'
CR=$'\r'
is_debug=0
sudocmd=""
bin_path="/opt/bin/bash"
script_path="$(dirname $0)"
echoerr "Home: $script_path"

if [[ -f "$bin_path" ]];then
    bin_path="$(dirname $bin_path)"
else
    bin_path=""
fi

source "$script_path/2fa.sh"
source "$script_path/opt.sh"
secret_key=$(cat "$script_path/secret_key.txt")

# -----------------------------------
function finish() {
    printf -- '-%.0s' {1..20}  1>&2
    echo ""  1>&2
}
trap finish EXIT

# -----------------------------------
echoheader() {
    headers="${headers}$@${CR}${LF}"
    echo "$@" 1>&2;
}

echobody() {
    txtBody="${txtBody}$@${LF}"
    [ "$is_debug" -gt 0 ] && echo "$@" 1>&2;
}

endheader() {

    RESPONSE_CODE="$1"
    echo "$RESPONSE_CODE" 1>&2;
   
    CONTENT_TYPE="$2"
    if [ -z "$CONTENT_TYPE" ];then
        CONTENT_TYPE="text/html; charset=utf-8"
    fi

    printf "HTTP/1.0 %s\r\n" "$RESPONSE_CODE"
    printf "Cache-Control: no-store,no-cache\r\n"
    printf "Content-Type: %s\r\n" "$CONTENT_TYPE"
    txtBody="${txtBody//%/%%}"
    byteCNT=$(printf "$txtBody" | wc -c)
    LineCNT=$(printf "$txtBody" | wc -l)
    printf "Content-Length: %d\r\n" "$byteCNT"
    printf "%s\r\n" "$headers" # End of headers

    # Printing the response body
    printf "%s" "$txtBody"
   
    d=`date '+%m-%d %T'`
    echoerr "[ ] $RESPONSE_CODE $LineCNT lines $byteCNT bytes | $HTTP_DEBUG"
    echoerr ""
    echoerr ""
    "$bin_path/"sleep 0.2
    exit 0;
}


# -----------------------------------
elapsed() {
    if [ "$1" -eq 0 ];then
        start_time=$(date +%s.%3N)
        echoerr "$2"
    else
        end_time=$(date +%s.%3N)
        echoerr "$2" $(echo "scale=3; x=$end_time - $start_time;if(x<1) print 0; x" | bc)
        start_time=$(date +%s.%3N)
    fi
}

# -----------------------------------
# Function to URL-decode a string
urldecode() {
    local url_encoded=$1
    local url_decoded=""
    local hex_part
    local char_part

    while [[ -n $url_encoded ]]; do
        if [[ $url_encoded =~ ^([^%]*)%([0-9a-fA-F]{2})(.*) ]]; then
            char_part="${BASH_REMATCH[1]}"
            hex_part="${BASH_REMATCH[2]}"
            url_encoded="${BASH_REMATCH[3]}"
            url_decoded+="${char_part}"
            url_decoded+=$(printf "\x$hex_part")
        else
            url_decoded+="$url_encoded"
            break
        fi
    done

    echo "$url_decoded"
}

# Function to get value by key
getValueByKey() {
    local input=$1
    local key=$2
    local value=""
    local mode="header"

    while IFS= read -r line || [[ -n "$line" ]]; do
        if [[ "$line" == "" ]]; then
            mode="body"
            continue
        fi

        if [[ "$mode" == "header" && "$line" == "$key:"* ]]; then
            value="${line#*: }"
            break
        elif [[ "$mode" == "body" && "$line" == *"$key="* ]]; then
            local temp="${line#*$key=}"
            value="${temp%%&*}"
            value=$(urldecode "$value")
            break
        fi
    done <<< "$input"

    echo "$value"
}

# -----------------------------------

    init_rules ipv4
    init_rules ipv6

# -----------------------------------
    LINES=`timeout 0.3s cat`
    LINE_CLEAN=$(echo -n "$LINES" | tr -d "\r" )
    echoerr "--LINE_CLEAN--"
    echoerr "$LINE_CLEAN"

# ---------------------------------------------------
    # Regular expression to match the HTTP method and path
    regex_path="^GET (.*) HTTP/1.[01]"
    if [[ "$LINE_CLEAN" =~ $regex_path ]];then
            path="${BASH_REMATCH[1]}"
            echoerr ""
            echoerr "path $path"
    fi

    regex_key="key=([0-9]{6})" #string="key=123456"

    if [[ "$LINE_CLEAN" =~ $regex_key ]]; then
        ClientIPv4="$(getValueByKey "$LINE_CLEAN" 'ipv4')"
        ClientIPv6="$(getValueByKey "$LINE_CLEAN" 'ipv6')"
        ClientIP_CF=$(getValueByKey "$LINE_CLEAN" 'cf-connecting-ip')

        echoerr ""
        echoerr "Extracted number: ${BASH_REMATCH[1]}"
        echoerr "ClientIP is $ClientIPv4 $ClientIPv6"
        echoerr "ClientIP CF is $ClientIP_CF"
        echoerr "SOCAT_PEERADDR is $SOCAT_PEERADDR"
        echoerr ""

        if [[ -z "$ClientIP" ]];then
            ClientIP="$ClientIP_CF"
        fi

        rt=$(totp $secret_key)
        if [[ "$rt" -eq "${BASH_REMATCH[1]}" ]]; then
            if [[ -z "$ClientIPv4" && -z "$ClientIPv6" ]];then
                if [[ "$ClientIP" == *.* ]]; then
                    add_ipv4 $ClientIP
                elif [[ "$ClientIP" == *:* ]]; then
                    add_ipv6 $ClientIP
                else
                    echoerr "Text does not contain ':' or '.'"
                    echobody "ClientIP is Missing"
                    endheader "202 Accepted" "text/plain; charset=UTF-8"
                fi
            else
                if [[ "$ClientIPv4" == *.* ]]; then
                    add_ipv4 $ClientIPv4
                fi
                if [[ "$ClientIPv6" == *:* ]]; then
                    add_ipv6 $ClientIPv6
                fi
            fi

            echoerr "PASS"
            echobody "OK"
            endheader "200 OK"

        else
            echoerr "BAD $rt"
            endheader "403 Forbidden"
        fi
    elif [[ "$path" == "/" ]];then
            echobody "$(cat "$script_path/index.html")"
            endheader "200 OK"
    else
        endheader "404 Not Found"
    fi
 
Thank you for your efforts and sharing!

Are you using non-common Ports? Does the rogue scanning still persist with them?
 
Are you using non-common Ports? Does the rogue scanning still persist with them?

Yes, I have been using high-numbered ports, specifically those above 5000 and forwarding to local 3389, but attempts to connect persisted. I noticed the same IP address from various locations around the world repeatedly trying to establish a connection.

1701240928612.png
 
Try port number ranges of 49152
I would think that only a former Commodore 64 user would remember that particular number LOL
 
I have collected a few paths that were repeatedly attempted for exploitation this week.
I am using an unusual port, and this is subsequent to the stunnel. Tons of non-HTTP incoming connections have been filtered out.
These were gathered from my home router.
I'm guessing the robots.txt is from search engine spiders.
GET /ab2g HTTP/1.1
GET /ab2h HTTP/1.1
GET /api/v2/static/not.found HTTP/1.1
GET /remote/logincheck HTTP/1.1
GET /sitemap.xml HTTP/1.1
GET /robots.txt HTTP/1.1

And few commonly seen user-agents, I can see two organizations scanning.
User-Agent: curl/7.64.1
User-Agent: Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)
User-Agent: Expanse, a Palo Alto Networks company, searches across the global IPv4 space multiple times per day to ident
ify customers&#39; presences on the Internet. If you would like to be excluded from our scans, please send IP addresses/
domains to: scaninfo@paloaltonetworks.com
User-Agent: Mozilla/5.0 (compatible; CensysInspect/1.1; +/)
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4 240.111 Safari/537.36
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0
User-Agent: Mozilla/5.0 zgrab/0.x
 
Similar threads
Thread starter Title Forum Replies Date
W Adding Unbound when you have Diversion Asuswrt-Merlin AddOns 5

Similar threads

Latest threads

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top