Apache Web サーバのセキュリティ設定と管理ガイド

【概要】 Web サーバの管理とセキュリティ強化に関する包括的な手順書である.サイトマップ生成プログラムの実装と,Apache Web サーバのメンテナンス方法を詳述している.サーバのセキュリティ設定として,バージョン情報の非公開化,OCSP Stapling の有効化,HSTS の設定,X-Frame-Options の設定,X-Content-Type-Options の設定,Content-Security-Policy の設定などを含む.各設定項目について,目的,実装手順,確認方法を体系的に解説している.これらの設定により,Web サーバのセキュリティレベルを向上させ,様々な攻撃からの保護を実現することができる.

サイトマップ sitemap.xml の作成プログラム

次のプログラムを a.py のような名前で保存し,「python a.py https://www.kkaneko.jp/index.html」のように実行することにより,サイトマップ sitemap.xml を作成することができる.

AI により推敲,修正されたプログラム)

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import sys
import re
import xml.etree.ElementTree as ET
import xml.dom.minidom

def is_valid_url(url, target_domain):
    """ URLが指定されたドメイン内で有効かチェック """
    parsed_url = urlparse(url)
    return parsed_url.netloc and parsed_url.scheme and parsed_url.netloc == target_domain

def check_image_links(page_soup, page_url, target_domain, session, broken_images):
    """ ウェブページ内の画像リンクをチェック """
    for img_tag in page_soup.find_all("img"):
        img_src = img_tag.get("src")
        if img_src:
            full_img_url = urljoin(page_url, img_src)
            if is_valid_url(full_img_url, target_domain) and not is_url_accessible(full_img_url, session):
                report_broken_link(full_img_url, page_url, broken_images)

def is_url_accessible(url, session):
    """ URLがアクセス可能かチェック """
    try:
        response = session.head(url)
        return response.status_code == 200
    except requests.RequestException:
        return False

def report_broken_link(url, source_url, broken_links):
    """ 壊れたリンクを報告 """
    print(f"切れたリンク: {url}, 元のページ: {source_url}")
    broken_links.add((url, source_url))

def remove_hash_fragment(url):
    """ URLからハッシュフラグメントを削除 """
    return re.sub(r"#.*", "", url)

def remove_query_parameter(url):
    """ URLからクエリパラメータを削除 """
    return url.split('?')[0]

def get_file_size(url, session):
    """ URLのファイルサイズを取得 """
    response = session.head(url)
    return int(response.headers.get('content-length', 0))

def get_all_links(page_url, target_domain, session, visited_urls, broken_images):
    """ HTML ファイル内のリンクを取得 """
    urls = set()
    if page_url.endswith(('.pdf', '.ppt', '.pptx', '.doc', '.docx', '.zip', '.ZIP', '.accdb', '.csv', '.txt', '.jpg', '.JPG', '.png', '.MOV', '.mp4', '.MP4', '.gz', '.z', '.blend', '.ply', '.wav', '.xls', 'xlsx', '.txt', '.MTS', '.iso', '.tif', '.tiff', '.bmp', '.mp3', '.avi', '.obj', '.cgi', '.spam', '.sh', '.tgz', '.ova', '.IFO', '.VOB', '.BUP', '.pcd', '.gif', '.xml', '.fbx', '.m4a', '.odp', '.mht', '.ttf', '.rb', '.tex')):
        return []

    # ファイルサイズのチェック
    if get_file_size(page_url, session) > 10000000:
        print(f"大きすぎるファイル: {page_url}")
        return []

    try:
        response = session.get(page_url)
        if response.history:
            print(f"リダイレクト, リダイレクト元 {page_url}, 最終的なURL {response.url}")
        page_soup = BeautifulSoup(response.content, "html.parser")
        check_image_links(page_soup, page_url, target_domain, session, broken_images)
        for a_tag in page_soup.find_all("a"):
            href = a_tag.get("href")
            if href:
                href = urljoin(page_url, href)
                href = remove_hash_fragment(href)
                href = remove_query_parameter(href)
                if is_valid_url(href, target_domain) and href not in visited_urls and href.endswith(('.html', '.pdf', '.ppt', '.pptx', '.doc', '.docx')):
                    response = session.get(href)
                    print("add", response.url)
                    urls.add(response.url)
    except (requests.RequestException, Exception) as e:
        print(f"リクエストのエラー: {page_url}, エラー詳細: {e}")
    return urls

def crawl_website(start_url, target_domain, session, visited_urls, broken_urls, broken_images):
    """ ウェブサイトを再帰的に巡回 """
    visited_urls.add(start_url)
    links = get_all_links(start_url, target_domain, session, visited_urls, broken_images)
    for link in links:
        if link not in visited_urls:
            crawl_website(link, target_domain, session, visited_urls, broken_urls, broken_images)

def create_sitemap(links):
    """ サイトマップをXML形式で作成 """
    urlset = ET.Element("urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
    for link in sorted(links):
        url = ET.SubElement(urlset, "url")
        ET.SubElement(url, "loc").text = link
        ET.SubElement(url, "changefreq").text = "monthly"
    return ET.ElementTree(urlset)

def save_sitemap(sitemap, filename):
    """ サイトマップをファイルに保存 """
    # XMLを文字列として生成
    xml_str = ET.tostring(sitemap.getroot(), encoding='unicode')

    # タグごとに改行を挿入
    xml_str = xml_str.replace('><', '>\n<')

    # ファイルに保存
    with open(filename, "w", encoding='utf-8') as file:
        file.write(xml_str)

# メインの実行部分
if len(sys.argv) > 1:
    start_url = sys.argv[1]
    target_domain = urlparse(start_url).netloc
    session = requests.Session()
    visited_urls, broken_urls, broken_images = set(), set(), set()

    crawl_website(start_url, target_domain, session, visited_urls, broken_urls, broken_images)

    # サイトマップの作成と保存
    sitemap_xml = create_sitemap(visited_urls)
    save_sitemap(sitemap_xml, "sitemap.xml")

    print("サイトマップが作成され、保存されました。")
else:
    print("起動引数にURLを指定してください。")

Apache Web サーバのメンテナンス

パッケージのアップデート操作

端末を開き,次のように操作を実行する(端末を開くには,右クリックメニューが便利である).

sudo apt -y update
sudo apt -yV upgrade

Apache Web サーバの更新

パッケージの情報のアップデート,Apache の更新,Apache のバージョン確認,および Apache の再起動を実行する.

sudo apt -y update
cd /etc
tar -cvpf /var/tmp/apache.$$.tar ./apache2
sudo apt -y upgrade apache2
apache2 -v
sudo systemctl restart apache2

Apache 2 サーバのバージョン情報を公開しない

Apache 2 サーバのバージョン情報を公開すると,攻撃者に対して不必要な情報を提供することになる.そのため,次の設定により,Apache 2 サーバのバージョン情報を公開しないようにする.

  1. /etc/apache2 下の .conf ファイルに,次の2行を追加する.
    • ServerTokens Prod: 応答ヘッダーに"Apache"とだけ表示する(バージョン番号やOS情報を含めない).
    • ServerSignature Off: Apacheが生成するエラーページにバージョン番号やOS情報を含めない.
    ServerTokens Prod
    ServerSignature Off
    
  2. .conf ファイルのテストを実行する.エラーメッセージが出ないことを確認する.
    sudo apache2ctl configtest
    
  3. Apache を再起動する.
    sudo systemctl restart apache2
    

Apache2でOCSP Staplingを有効にする

OCSP Stapling を利用することにより,SSL/TLS証明書の有効性を確認する際のプライバシーが保護される.

  1. SSLモジュールが有効であることを確認する.
    sudo a2enmod ssl
    
  2. /etc/apache2 下の SSL 設定の .conf ファイルに,次の2行を追加する.
    SSLUseStapling on
    SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
    
  3. .conf ファイルのテストを実行する.エラーメッセージが出ないことを確認する.
    sudo apache2ctl configtest
    
  4. Apache を再起動する.
    sudo systemctl restart apache2
    
  5. OCSP Stapling の確認を行う.「mydomain.com」のところは,自分のドメイン名に変更して実行し,OCSP レスポンスが表示されることを確認する.
    openssl s_client -connect mydomain.com:443 -status
    

Apache2でHTTP Strict Transport Security (HSTS) を設定

HTTP Strict Transport Security (HSTS) は,ブラウザにHTTPSを使用してサイトにアクセスするように指示するセキュリティ機能である.これにより,中間者攻撃やSSLストリッピング攻撃を防止することができる.

  1. header モジュールを有効にする.
    sudo a2enmod headers
    
  2. /etc/apache2 下の SSL 設定の .conf ファイルの VirtualHost セクション内に,次の1行を追加する.これにより,Webのクライアントに対して,約1年間HTTPSを使用してサイトにアクセスするように指示する.
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    
  3. .conf ファイルのテストを実行する.エラーメッセージが出ないことを確認する.
    sudo apache2ctl configtest
    
  4. Apache を再起動する.
    sudo systemctl restart apache2
    

X-Frame-Optionsヘッダーの設定

X-Frame-Optionsヘッダーは,クリックジャッキング攻撃を防止するためにウェブサイトが他のサイトのフレーム内で表示されることを防ぐ機能である.

  1. header モジュールを有効にする.
    sudo a2enmod headers
    
  2. /etc/apache2 下の SSL 設定の .conf ファイルの VirtualHost セクション内に,次の1行を追加する.この設定により,ページが同一オリジンのフレーム内でのみ表示されることを指定する.
    Header always set X-Frame-Options "SAMEORIGIN"
    
  3. .conf ファイルのテストを実行する.エラーメッセージが出ないことを確認する.
    sudo apache2ctl configtest
    
  4. Apache を再起動する.
    sudo systemctl restart apache2
    

X-Content-Type-Option ヘッダーの設定

X-Content-Type-Options ヘッダーを設定することにより,ブラウザに対してMIMEタイプのスニッフィングを行わないように指示し,セキュリティを強化する.

  1. header モジュールを有効にする.
    sudo a2enmod headers
    
  2. /etc/apache2 下の SSL 設定の .conf ファイルの VirtualHost セクション内に,次の1行を追加する.この設定により,ブラウザに対してサーバーが指定したMIMEタイプを変更せずに使用するように指示する.
    Header always set X-Content-Type-Options "nosniff"
    
  3. .conf ファイルのテストを実行する.エラーメッセージが出ないことを確認する.
    sudo apache2ctl configtest
    
  4. Apache を再起動する.
    sudo systemctl restart apache2
    

Content-Security-Policy ヘッダーの設定

Content-Security-Policy (CSP) ヘッダーを設定することにより,Webサイトにおけるクロスサイトスクリプティング (XSS) 攻撃などのセキュリティリスクを軽減するために,リソースの読み込み方法をブラウザに指示する.

  1. header モジュールを有効にする.
    sudo a2enmod headers
    
  2. /etc/apache2 下の SSL 設定の .conf ファイルの VirtualHost セクション内に,次の1行を追加する.「script-src」は指定された信頼できるスクリプトソースからのスクリプトのみを許可することを示す.「https://translate.google.com」のところには,信頼できるスクリプトソースを設定すること.
    script-src 'self' https://translate.google.com; object-src 'none';"
    
  3. .conf ファイルのテストを実行する.エラーメッセージが出ないことを確認する.
    sudo apache2ctl configtest
    
  4. Apache を再起動する.
    sudo systemctl restart apache2