Bitcoin Forum
February 09, 2026, 01:41:33 PM *
News: Latest Bitcoin Core release: 30.2 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: [Read] How to harden your service from attacks  (Read 28 times)
NotATether (OP)
Legendary
*
Offline Offline

Activity: 2240
Merit: 9420


Trêvoid █ No KYC-AML Crypto Swaps


View Profile WWW
February 08, 2026, 10:05:06 AM
Last edit: February 08, 2026, 12:49:25 PM by NotATether
Merited by Pmalek (5)
 #1

So you have just launched your service and posted it on Bitcointalk. Congratulations! Now you have to secure your infrastructure before someone abuses it and causes you downtime. It could be a hacker, a competitor, a disgruntled customer, or anyone else, but knowing how to properly lock down your services will save you a lot of trouble.

Major hacks are often fatal to new services. Most don't survive the reputational fallout that follows it. Therefore, following these simple steps ensures that you never have to deal with those situations.

Table of Contents




0. Prerequisites

If you can't answer YES to all of these, then you need to find a specialist to secure your infrastructure for you. (I am available for hire, offers can be sent to my inbox or my Telegram/email address)

  • You are comfortable with using the Linux console.
  • You know how IP addresses, TCP, and port binding work.
  • You can add rules to a firewall without locking yourself out of SSH.
  • You can write web server and Tor configs without help.
  • You have reasonable experience in shell scripting.
  • You understand how browsers fetch web pages from servers.

Once the basics are covered, it’s time to move from theory into defensive posture. Most attacks that hit new services are boring, automated, and completely preventable.

Bots scan IPv4 nonstop. They are not targeting you. They are targeting anything that answers.

Your job is to make your host uninteresting.



1. Mass SSH Brute-Force

Bots constantly try username/password combinations against port 22. Even tiny VPS instances see thousands of attempts per day. If you have set a complex password, this isn't a big problem, but people with 8-character passwords should pay attention.

Most people configure SSH correctly so this has probably already been done on your server, but I'm leaving this here for completeness anyway.

Solution:

There are three different solutions but you can apply all three at once for maximum security. The first one is to stop using passwords and to use keypairs only. First you have to add a public key to the server:

Code:
ssh-keygen -t ed25519 -a 100 -C "your-email-or-label"  # Linux, Mac OS, and Windows 10+

Type a name or just press enter to use the name id_ed25519, then create a password for encrypting your private key with.

Then install the public key to the server:

Code:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip   # it asks for your server password to copy the public key
chmod 600 ~/.ssh/id_ed25519

# You can also copy the key manually into the path, but make sure you fix permissions on the remote host
chmod 600 ~/.ssh/*
chmod 700 ~/.ssh

Then you disable passwordless login:

Code:
sudo vim /etc/ssh/sshd_config

# Change the following two settings to as follows, or add them if they don't exist
# PasswordAuthentication no
# PubkeyAuthentication yes

# Then restart ssh
sudo systemctl restart ssh

The second solution is to ban IP addresses that keep sending invalid usernames/passwords (the default is 3 in a row).

Code:
sudo apt install fail2ban
sudo systemctl enable --now fail2ban

The third solution is to change the default SSH port to something other than 22, via the sshd_config, but this is overkill and not strictly necessary if you have already applied the two other mitigations.



2. Web login / API brute force

Attackers try to brute-force logins against admin panels, WordPress, custom dashboards, and other sorts of APIs The symptoms are similar to what you would see in SSH brute force, but the solution needs to be applied at the web server level.

Rate limit requests in Apache:

Code:
sudo apt install -y libapache2-mod-evasive
sudo a2enmod evasive

# add this to /etc/apache2/mods-enabled/evasive.conf
<IfModule mod_evasive20.c>
    DOSHashTableSize    3097

    # rate limiting per page per IP
    DOSPageCount        5
    DOSPageInterval     60    # seconds

    # rate limiting across all pages per IP
    DOSSiteCount        50
    DOSSiteInterval     60

    DOSBlockingPeriod   300
</IfModule>

# Apply changes
sudo systemctl reload apache2

For nginx - recommended, more granular control than Apache:

Code:
# /etc/nginx/nginx.conf
http {
    # add this to the http block
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;  # adjust limits accordingly

    ...
}

# /etc/nginx/sites-available/some-site.conf
# example server
server {
    listen 443 ssl;

    location /login {
        limit_req zone=login burst=5 nodelay;  # add this line
    }
}

# Apply changes
sudo systemctl reload nginx

If you are using Cloudflare with nginx then to make rate limiting work properly, you must set the IP address from the forwarded IP.

Code:
# /etc/nginx/conf.d/realip.conf
real_ip_header CF-Connecting-IP;

set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# etc...  get IP ranges from  https://www.cloudflare.com/ips-v4, https://www.cloudflare.com/ips-v6, or just script it

Adjust for other CDNs accordingly.



3. Vulnerable software & outdated plugins

The number one cause of exploits. Bots test known exploit paths seconds after CVEs become public. More knowledgeable hackers might take days but can find deadlier exploits.

The solution for this is quite easy. Keep all of your software updated automatically.

Debian/Ubuntu:

Code:
sudo apt update
sudo apt install -y unattended-upgrades apt-listchanges

# add the following to /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";  # 1 = daily
APT::Periodic::Unattended-Upgrade "1";

# add the following to /etc/apt/apt.conf.d/50unattended-upgrades
"${distro_id}:${distro_codename}-security";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";  # using system's timezone
Unattended-Upgrade::Remove-Unused-Dependencies "true";

Red Hat:

Code:
sudo dnf install -y dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer

# add this to /etc/dnf/automatic.conf
[commands]
upgrade_type = security
download_updates = yes
apply_updates = yes
reboot = when-needed



4. Exposed internal services

It should not be so easy to have databases and microservices listening on public interfaces, but it's common, and this is a nasty vulnerability that needs to be patched immediately.

The solution is different for each service because they have different config files, but to quickly find all of the exposed ports, run this command:

Code:
ss -tulpn src 0.0.0.0

Then firewall the services or bind them to localhost accordingly.



5. DDoS & resource exhaustion

Commercial DDoS services are available for quite cheap, so as a result, DDoS attacks are like mosquitoes bothering you at night. You can't just block IP addresses with the firewall because they keep changing.

Everyone knows the symptoms:
  • Load average explodes
  • SSH lags
  • Complete cut-off of service to the web resource

Personally I have a lot of experience with DDoS mitigation, and I have written about it elsewhere which I will quote here. Most of these solutions are only possible when you have big company infrastructure, but a one-man shop like you can apply a few of these configs on your server for basic DDoS protection.

Quote
Disclaimer: To roll our your own load balancing, you need quite a lot of money. Load balancers like F5 BIG-IP are really expensive and go into the 5 figures. But I guess for a mixer that makes tens of millions of dollars in profit a month, that is a drop in the bucket. Also there is Kemp LoadMaster that's cheaper (around thousands, rather than tens of thousands).

You will need a team of people to deal with networking maintenance of this hardware so it's really only reasonable if you partner with a datacenter company who offers such hardware.

Assuming you don't want to buy a hardware load balancer for some reason, you can make a DIY load balancer with HAproxy like this:

Code:
frontend web_frontend
    bind <public-ip>:80
    mode http
    acl abuse src_http_req_rate(10s) gt 100
    http-request deny if abuse
    default_backend web_servers

backend web_servers
    mode http
    balance roundrobin
    server server1 <private-ip-1>:80 check
    server server2 <private-ip-2>:80 check
    ... add more as needed ...

 # block layer 7
acl invalid_user_agent hdr(User-Agent) -m len 0
http-request deny if invalid_user_agent

or just purchase a license to RELIANOID if you don't want to write a bunch of config yourself.

But this method is less robust against DDoS attacks which nowadays can overwhelm 10Gbps Etherenet cables and Linux software.

Now you need to configure iptables to drop excessive connections from IP addresses:

Code:
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 50 -j DROP

This sinkholes IP addresses that open more than 50 concurrent TCP connections to HTTP port 80. This doesn't stop the requests themselves but it prevents your own server from uploading reply spam packets to them, which massively improves performance during an attack.

Here is the config I used to drop all offending connections when BitMixList was under DDoS attack some time ago.

Code:
 ###################################################################
 # Anti-DDoS protection                
 # cuts off all upload traffic to DDoS IPs
 # Credit: https://web.archive.org/web/20220720030447/https://javapipe.com/blog/iptables-ddos-protection/
 # place this in /etc/sysctl.d/99-antiddos.conf and reboot the server

kernel.printk = 4 4 1 7      
kernel.panic = 10                      
kernel.sysrq = 0            
kernel.shmmax = 4294967296          
kernel.shmall = 4194304    
kernel.core_uses_pid = 1
kernel.msgmnb = 65536            
kernel.msgmax = 65536          
vm.swappiness = 20      
vm.dirty_ratio = 80        
vm.dirty_background_ratio = 5  
fs.file-max = 2097152      
net.core.netdev_max_backlog = 262144
net.core.rmem_default = 31457280
net.core.rmem_max = 67108864
net.core.wmem_default = 31457280
net.core.wmem_max = 67108864
net.core.somaxconn = 65535  
net.core.optmem_max = 25165824  
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
net.ipv4.neigh.default.gc_interval = 5                                                  
net.ipv4.neigh.default.gc_stale_time = 120
net.netfilter.nf_conntrack_max = 10000000
net.netfilter.nf_conntrack_tcp_loose = 0
net.netfilter.nf_conntrack_tcp_timeout_established = 1800
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 20  
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 20
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 20
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 10
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.ip_no_pmtu_disc = 1
net.ipv4.route.flush = 1
net.ipv4.route.max_size = 8048576
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_congestion_control = htcp
net.ipv4.tcp_mem = 65536 131072 262144
net.ipv4.udp_mem = 65536 131072 262144
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.udp_rmem_min = 16384
net.ipv4.tcp_wmem = 4096 87380 33554432
net.ipv4.udp_wmem_min = 16384
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_orphans = 400000
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_fack = 1
net.ipv4.tcp_ecn = 2
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 10
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1
# Uncommenting this will break all of your bridged network interfaces like Docker (docker0), libvirt (virbr0), LxC (lxcbr0) etc. on this server and destroy VMs and containers networking, so I don't use this
#net.ipv4.ip_forward = 0

But the most powerful anti-DDoS method is BGP with anycast.

You can literally deploy swarms of servers and then the BGP protocol will just redirect traffic to another server when one goes down to DDoS (after you take the attacked server off the BGP network of course). Repeat ad infinitum. And Anycast is a form of networking where you assign an IP address to many different servers.

There's a lot of software to make a server announce Anycast IPs to the network, like Quagga, BIRD, FRRouting, and so on. Their config looks something like this:

Code:
# For BIRD
router id <router-ip>;
protocol bgp {
    local as <your-asn>;
    neighbor <upstream-router-ip> as <upstream-asn>;
    export all;
    import all;
}

As you can see, this requires your own ASN (Autonomous System Number - a cluster of IP addresses that are owned by you) and you will need to cooperate with your ISP to receive BGP packets to your router. Costs a lot of money as well.

You should combine all of these methods to protect your web service. Maybe they are not feasible for mixer operators which are usually small groups of 1-5 people, but you should definitely coordinate with a local, large, hosting provider which allows such websites such as mixing, to provide you DDoS protection.



5.1. Origin server hardening

Cloudflare is not a holy grail. Cloudflare and other CDNs protect layer 7 (i.e. HTTP) traffic from flooding your server, but if someone can find your IP address by another way, they can bypass your CDN and attack your server (forcing you to use all of the mitigations I wrote above).

You might ask, how is this possible?

First of all, there are usually two ways for the public to access your website:

- By domain name (https://example.com:443)
- By IP address (http://11.22.33.44:80) <-- no real users do this

Basically what people do is reverse-lookup the IP address of your domain name, and usually they will get a CDN IP. However, IPv4 scanning is very simple and low-cost to perform (it's how the SSH brute-force bots from section 1 were made possible), some people just scan entire hosting providers' subnets - now the hosting providers know this so they send abuse reports to the provider who then suspends the server fairly quickly.

Once finding your IP address like this they can flood your web server port with TCP packets, SYN spam, etc etc.

The way to prevent this is to disable IP address resolution at the firewall level, and force users to only access your site by domain name. First, you must set up your website with a CDN, as you will be blocking all other source IP addresses.

Paste this into your shell:

Code:
sudo apt install -y nftables
sudo systemctl enable --now nftables
sudo nft -f - <<'NFT'
table inet filter {
  set cf4 { type ipv4_addr; flags interval; }
  set cf6 { type ipv6_addr; flags interval; }

  chain input {
    type filter hook input priority 0;
    policy drop;

    iif "lo" accept
    ct state established,related accept

    # SSH (adjust if needed)
    tcp dport 22 accept

    # Only Cloudflare may reach HTTP/HTTPS
    tcp dport {80, 443} ip  saddr @cf4 accept
    tcp dport {80, 443} ip6 saddr @cf6 accept

    # Add other ports here as necessary
    # All other inbound traffic will be blocked

    # Optional ping
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
  }
}
NFT

Create the following script at /usr/local/sbin/nft-update-cloudflare.sh

Code:
#!/usr/bin/env bash
set -euo pipefail

CF_V4_URL="https://www.cloudflare.com/ips-v4"
CF_V6_URL="https://www.cloudflare.com/ips-v6"

TABLE_FAMILY="inet"
TABLE_NAME="filter"
SET4="cf4"
SET6="cf6"

tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

# Ensure sets exist (no-op if they already do)
nft list set ${TABLE_FAMILY} ${TABLE_NAME} ${SET4} >/dev/null 2>&1 || \
  nft add set ${TABLE_FAMILY} ${TABLE_NAME} ${SET4} "{ type ipv4_addr; flags interval; }"

nft list set ${TABLE_FAMILY} ${TABLE_NAME} ${SET6} >/dev/null 2>&1 || \
  nft add set ${TABLE_FAMILY} ${TABLE_NAME} ${SET6} "{ type ipv6_addr; flags interval; }"

# Build a valid nft script (TOP-LEVEL commands)
{
  echo "flush set ${TABLE_FAMILY} ${TABLE_NAME} ${SET4}"
  while read -r cidr; do
    [[ -n "$cidr" ]] && echo "add element ${TABLE_FAMILY} ${TABLE_NAME} ${SET4} { $cidr }"
  done < <(curl -fsS "$CF_V4_URL")

  echo "flush set ${TABLE_FAMILY} ${TABLE_NAME} ${SET6}"
  while read -r cidr; do
    [[ -n "$cidr" ]] && echo "add element ${TABLE_FAMILY} ${TABLE_NAME} ${SET6} { $cidr }"
  done < <(curl -fsS "$CF_V6_URL")
} > "$tmp"

nft -f "$tmp"

Run it:

Code:
sudo chmod +x /usr/local/sbin/nft-update-cloudflare.sh
sudo /usr/local/sbin/nft-update-cloudflare.sh

If you use another CDN, update the script accordingly with their page of IP ranges.



6. Malicious request/command execution

These have a variety of different names (Cross-Site Request Forgery, Cross Site Scripting, SQL injection, command injection, execution from uploaded files, web shells, etc., and I will try to cover how to defend yourself from each one.



6.1. SQL injection

Never build SQL with string concatenation. Always use prepare statements i.e. parameterized queries ($pdo or $wpdb). For dynamic ORDER BY / column names, allowlist the column names and directions using a check in code since you can’t parameterize identifiers safely.

Examples:

Code:
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status");
$stmt->execute([
  ":email" => $email,
  ":status" => $status,
]);
$rows = $stmt->fetchAll();

global $wpdb;
$sql = $wpdb->prepare(
  "SELECT * FROM {$wpdb->users} WHERE user_email = %s",
  $email
);
$row = $wpdb->get_row($sql);



6.2. Command injection / shell injection

Don’t call a shell with user input. Prefer native APIs (filesystem, image libraries, HTTP clients). If you must run a process: use exec without a shell and pass args as an array.

JS example:

Code:
import { spawn } from "node:child_process";

// validate inputPath and outputPath somewhere here

const child = spawn("/usr/bin/convert", ["-resize", "200x200", inputPath, outputPath], {
  shell: false,
});  // safe

For PHP: Never use system/exec/shell_exec. If unavoidable, use strict allowlists for program name / args along with escapeshellarg, but treat this as last resort.



6.3. Cross-Site Scripting (stored/reflected/DOM)

It means your code accidentally runs user input. Usually this is the fault of PHP, but some people manage to mess this up with other programming languages and even pure Javascript too.

Example: If you use DOM to print user input in a <div>, and the user typed <script>alert(1);</script>, it will execute Javascript instead of showing output.

Another example:

Your code:
Code:
<input value="USERNAME">

User input:
Code:
" autofocus onfocus=alert(1) "

What your code creates in the DOM
Code:
<input value="" autofocus onfocus=alert(1) ">     !!! code execution

Escape at the moment you output, right before it renders. All of these characters should be escaped:

Code:
& < > " '

Encode URLs and check the scheme before embedding. Or, avoid embedding URLs in the first place. That's how "click link to run malware" hacks work.

If you are embedding something in Javascript (not HTML), encode the string to JSON first because "abc" is valid JSON but something like '"; evil_command(); //' will escape all of the quotes, which then breaks the output.

Set a CSP to prevent external scripts from running. Start with the following, then expand as needed (scripts, cdn, etc.).

In PHP, disable uploaded file execution in your configuration like this:

Code:
php_admin_flag engine off

Code:
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'



6.4. Server-Side Request Forgery

In general, never render anything from user-controlled strings without escaping them first, and only allow the user code to embed what is absolutely necessary, using maps or dictionaries to map user identifiers with the actual target values to embed/run/etc., and set appropriate timeout & size limits.

If you must allow users to submit URLs as input you must block the following addresses, otherwise the mitigations you've applied in section 5.1 will be breached:

Code:
Loopback: 127.0.0.0/8, ::1
Private: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
Link-local: 169.254.0.0/16, fe80::/10
Unique local IPv6: fc00::/7
Cloud metadata (varies): commonly 169.254.169.254 or provider-specific hostnames


Some other things you have to defend against:

  • DNS rebinding - map to IP addresses instead
  • Alternate IP formats like 2130706433, octal, hex
  • @ userinfo tricks such as https://allowed.com@169.254.169.254/
  • Embedded newlines / header injection
  • Redirects to internal IPs

It is better to run your services inside Docker or K8s containers so you can then configure an allowlist for those processes without affecting the rest of your system.



7. Data exposure via search engines

An embarrassingly common occurrence where admins leave unnecessary files in their web root, which then become downloadable by Google, using a practice called "dorking".

To mitigate this, you have to clean your web root folder often, remove things like .bak and .env files from it which are not supposed to be served, or add a location block in your web server to make them resolve to 404 (not to 403 because then people will figure out that the files exist).



8. Closing words

Prevention is better than cure.

You are not hiding from attackers.

You are building layers that make automated exploitation fail so the attacker moves to the next target.

Most criminals choose the easiest victim.

Don’t be it.

 
 b1exch.io 
  ETH      DAI   
  BTC      LTC   
  USDT     XMR    
.███████████▄▀▄▀
█████████▄█▄▀
███████████
███████▄█▀
█▀█
▄▄▀░░██▄▄
▄▀██▄▀█████▄
██▄▀░▄██████
███████░█████
█░████░█████████
█░█░█░████░█████
█░█░█░██░█████
▀▀▀▄█▄████▀▀▀
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!