Tell BLOCK from detection in Sakura VPS SiteGuard logs

In recent weeks, a series of unauthorized web access incidents have come to light, starting with Park24 Group's Times Car service and including a large-scale security incident in which GMO Internet was subjected to a cyberattack.

During one of those weeks, I took a look at the recent logs for SiteGuard Lite on my Sakura VPS. What I found were requests directly accessing the names of configuration files and environment variable files, requests attempting to traverse directories, attacks targeting other products not running on this server, and unfamiliar HTTP methods.

In the lines I checked, the verdict was BLOCK. The aim was to reduce entry points, so that traffic currently stopped in front of the app would not spread if it got through.

The environment is Sakura VPS, RHEL-based, Apache httpd, firewalld, PHP (php-fpm), and SiteGuard Lite. Multiple websites, including WordPress and EC-CUBE, reside on it. The following hostnames and IP addresses are all examples.

How to read logs

From one line, I kept only these four things.

  • When it arrived
  • What type of URL is it? (e.g., configuration file name, administrative path, unusually long query, etc.)
  • Is the judgment BLOCK, or is it just detection and letting it through?
  • How many requests came from the same source in a short time

A "BLOCK" status means the request did not reach the application. A high number of BLOCK requests indicates active searching, not proof of intrusion.

This article will distinguish between different stopping methods using the following three terms.

  • BLOCK ...SiteGuard rejects it before it reaches the app.
  • 403 ...Apache rejects the request.
  • DROP ...firewalld drops the packet, so from the sender it looks as if nothing arrived
A diagram splitting one log line into when, which URL, the verdict, and the same source. Addresses are examples.
These are the four things to extract from a single line of the log. The address and hostname are just examples.

Policy

The primary method will be using SiteGuard's official signature. The IP list is kept as a supplement. Since the source changes daily, manually tracking the list will not be the main countermeasure.

The actions taken were in the following order, starting with those closest to the log.

  1. Close files that do not need to be made public.
  2. Make unused hostnames invisible from the outside.
  3. Put the app on a security release in its own product line, and limit the app admin screen to the work network
  4. Remove the version string from the response header.
  5. For a while, automatically keep away IPs that repeat the same failures
  6. Automatically apply only OS security updates without requiring a restart.
  7. DROP only the sources that stand out, as a supplement
  8. Restrict SiteGuard's management port to only the work line.
Layers a request passes through: firewalld, SiteGuard, unknown hosts, allowed IPs, patches, and the app.
Most of these requests stop at SiteGuard. firewalld is only a supplement for sources that stand out.

1. Close information that had been left exposed.

Requesting the server default document root by raw IP showed that info.php was returning 200. The body was phpinfo, listing the PHP version, paths, and loaded modules. It had been left there for diagnostics, so I moved the file and returned 403 for that path. I also changed the default document root so it does not show its contents.

I also checked each site's .env and web.config from the outside, using only the status code. All were 403 or 404, and no body was returned. That confirmed they were closed. I added no further deny rule.

Only the HTTP methods necessary for the site were retained. Methods not used for normal browsing or form submission are rejected by Apache.

A browser showing Forbidden for a direct request to the server IP. The address is hidden.
Entering an IP address directly will result in a Forbidden error. Previously, internal PHP information was displayed here.

2. Unused sites and hosts with only a name.

For sites that weren't functioning, I decommissioned them along with their document roots, virtual hosts, and unnecessary databases. The databases and users of the sites I kept were not touched.

I kept the DNS wildcard.* As long as this exists, deleted names and non-existent subdomains will still be resolved to this server. Therefore, HTTPS connections from hosts other than those I know will result in a 403 error. This is to prevent unknown names from responding with the server's default certificate.

Before and after the work, I checked that the public sites returned HTTP 200, and checked the certificate issuer and expiry.

A diagram of wildcard DNS: the name resolves, and unknown hosts get 403. Hostnames are examples.
The name still resolves. Open only hosts that exist, and return 403 for the rest. Hostnames are examples.

3. App-side entry point

EC-CUBE was moved to a security release in its own product line. The change was the multi-factor authentication around the admin screen. I put production into a short maintenance window, took a database dump, applied the official diff, updated the schema, then ended maintenance. I checked the version string, related files, and the front and admin login responses, then called it done. Customizations such as addresses and invoices stayed.

WordPress core and plugins were already current at the time. The only addition was rejecting xmlrpc.php. That unused path is an easy route for pingbacks and brute force, so I returned 403.

The EC-CUBE admin panel had an empty field for allowed connections. I entered only the IP address of my work network here. On days when I connect from a different network, I add that IP address and clear the cache.

I restricted access to the temporary PHP file I had been using for work, which was previously left unauthenticated, to only the same network connection. Although it wasn't a file that executed commands, it allowed me to see its internal paths and the progress of the work. I deleted it once I was finished.

On a network that is not allowed, the app admin screen says access is denied.
The app's management screen will not open from unauthorized connections. The site name and address are hidden.
From the work network, the app login screen opens.
The login screen is accessible from the work network.

4. Do not include the version in the header.

By default, PHP adds X-Powered-By: PHP/x.x.x to the response headers.expose_php=Off. After reloading php-fpm, this header disappears. The page does not change. This does not close the vulnerability. It only makes the version harder to guess.

expose_php = Off

5. Leave the repetition to fail2ban.

I installed fail2ban and set firewalld to DROP, for a while, IPs that keep failing or probing in the logs. I enabled these six jails.

JailWhat it watches
sshdSSH login failed
apache-authFailures such as Basic Authentication
apache-badbotsKnown malicious crawlers
apache-noscriptProbing for scripts that do not exist
apache-overflowsUnusually long URLs, etc.
recidiveBlock IP addresses that have been blocked multiple times for an extended period.

I added the work network to ignoreip so my own actions would not lock me out.

A side effect is that legitimate users can be cut off for a while. A company or carrier egress IP is sometimes shared with unrelated people. After enabling it, use fail2ban-client status to check the counts, and loosen or remove a jail that looks wrong.

01

🚪

Few failures

Still let it through

02

⚠️

Repeat in a short period of time

The same sender continues

03

🔒

Close for a while

Temporarily DROP

If the same sender repeatedly fails in a short period of time, I'll keep the door closed for a while.

6. Automate only the OS security updates.

dnf-automatic and apply only security updates automatically. I do not reboot automatically after a kernel update. The scope is narrower than updating everything, but it avoids an unplanned reboot taking the site down.

7. Block frequently occurring IP addresses as a backup.

I added only the source that stood out in the most recent logs to both SiteGuard's connection rejection and firewalld's DROP command. I didn't add country-specific rejections because there are legitimate access attempts from overseas.

This is an initial step. Even if the same address continues to appear in the log afterward, if it's already blocked, we don't manually move each entry to the DROP list. Our primary focus remains on the official signature and its automatic updates. Even within the same campaign, the sender changes frequently.

We don't blindly apply the publicly available list of notorious IP addresses. Older addresses and shared cloud ranges can sometimes inadvertently include legitimate communications. Manually adding only those entries that clearly indicate a scan from our own server logs is sufficient.

8. SiteGuard's management port is only for the work line.

The SiteGuard admin screen listens on port 9443. It is open only to the work network and localhost. Connections from anywhere else time out. Ports 80 and 443 of the public site are unchanged.

This screen uses a different certificate than the one used by the production site's Let's Encrypt. If you open it by directly specifying the IP address, your browser will display a certificate warning. You can open it by proceeding to the details from an authorized connection.

From elsewhere, reach it with an SSH local forward.

ssh -L 9443:127.0.0.1:9443 user@your-server

In a local browser, open https://127.0.0.1:9443/ . A certificate warning is likely. If the work-network IP changes, update the firewalld allow rule. Forgetting that locks out only you.

A screen showing that the admin port times out from a network that is not allowed.
Connections from unauthorized lines will time out.
The SiteGuard login screen after continuing past the certificate warning.
On authorized connections, the login screen appears after exceeding the certificate warning. The address is hidden.
The SiteGuard admin screen after login. The hostname and source list are hidden.
The management screen after logging in. Only the signature breakdown is visible.

Left for later

The following three were considered but ultimately rejected.

Additional IPv6 restrictions

This VPS did not have an IPv6 address at the time. The DNS entry AAAA remained. The name resolved to IPv6, but the server did not respond in IPv6. I decided that it would be sufficient to simply ensure that all but SSH, HTTP, and HTTPS were closed via firewalld just before enabling the address.

Automatic DROP based on detection logs

This mechanism sends requests that the WAF has already rejected to firewalld based on their number. It is prone to involving shared IPs and cloud addresses for payment and APIs. Skipping the notification stage and automatically blocking them would have significant side effects given the current volume of requests.

Constantly synchronize attack IP addresses with multiple other VPS servers.

The same campaign can hit several servers around the same time. Sources are often disposable, and a mistaken drop spreads to every server. It is easier to let SiteGuard BLOCK on each server, and to add by hand only what stands out in that server's log.

Done for this round

What went in

  • Files that do not need to be made public and unknown hosts have been closed.
  • I updated the app and made the management screen accessible only through the work network.
  • I installed fail2ban and the OS security updates.
  • The admin port, and only the sources that stood out, were dropped as a supplement.

When a reason appears

Add it then

  • The day to enable IPv6
  • A day when the same source still surges after it has been blocked
  • The day when only the production management URL is being attacked repeatedly
  • A day when the same campaign stands out at once on several other servers
Some things are meant to be finished this time, while others can wait until a suitable opportunity arises.

Leave it so that it can be restored.

I copied the configuration files I was going to modify, noting the date and time, before starting the work. When updating the application, I left a database dump in a separate directory.

The public site will be checked for the following changes each time they are made.

  • Whether the homepage returns HTTP 200
  • Certificate issuer and expiration date
  • Will the app's management screen only open from authorized connections?
  • Is the environment variable file still showing 403 or 404?

What to watch afterward

I've finished the implementation for now. I'm continuing with the following three things:

  • If the IP address of your work network changes, update the SiteGuard management port and the permissions for the app's management screen.
  • Check SiteGuard's detections once a week. See if any new types of URLs have been added.
  • If the production admin URL keeps being targeted, the same IP surges after a DROP, or real harm appears, add something then.

The sources that stood out the first time are included. Everything else is left to the official signature BLOCK. I do not copy busy log lines over by hand each time.

Checklist

The steps to take when transferring to another VPS are as follows.

  1. Check that the log you checked is judged as BLOCK.
  2. Stop exposing phpinfo and the standard document root.
  3. The status code confirms that the environment variable file cannot be read from outside.
  4. Delete unused hosts and set unknown hosts to 403 for wildcard access.
  5. Move the app to a security release in its own product line, and reject the unused xmlrpc.php.
  6. Enter the allowed IP addresses in the app's management screen.
  7. expose_php=Off .
  8. Install fail2ban and exclude the work line from being targeted.
  9. The OS will only have automatic security updates, and restarts will remain manual.
  10. Only add the most prominent sources to SiteGuard and firewalld.
  11. SiteGuard's management ports should be limited to the work network and localhost.
  12. After the changes are reflected, check the response and certificates on the public site.

It's impossible to eliminate entry points entirely. New sources, unknown attacks, and changes in operational lines will remain. The benefit of this approach lies in reducing the amount of material available for reconnaissance and unused entry points, and mitigating the impact of any slip-throughs through updates and reduced privileges.