8割解けるCTF「WEST-SEC」

セキュリティ初心者の方でも楽しめるゲーム形式のセキュリティイベント。CTFや勉強会の依頼があればご相談ください。

Linuxの基本操作

1.WEST-SECで利用するLinux環境

WEST-SECで利用するLinux環境は、主にAmazonLinuxです。CentOSベースなので、CentOSのコマンドがほぼ使えます。
たまに、利用するツールなどの関係でUbuntuを使うこともあります。

2.Linuxコマンド

WEST-SECでは、Linuxサーバの操作が必要な場合があります。基本コマンドをある程度理解し、それ以外は「検索すればいいじゃん」という考えでいいと思います。

(1)基本コマンド

ファイルを表示するls、パスを移動するcd、ファイルをコピーするcp、ファイルを削除するrm、ファイルの中身を表示する(catやless)、検索するgrepなどがあります。
以下のサイトがわかりやすいです。
qiita.com
qiita.com

・事前作業(主催者)
以下のフォルダを作り、読み書き実行権限(Read:4 + Write:2 + Execute:1 =7)を付与しています。

mkdir /var/www/html/user
chmod 757 /var/www/html/user   #otherのユーザにw権限を与えるという意味で、chmod o+w /var/www/html/user でもいい。 

または以下を実施

for i in {1..25};do
  mkdir /var/www/html/team${i}
  chown team${i}.team${i} /var/www/html/team${i}
done

・皆さんの作業

cd /var/www/html/team4/         <--自分のユーザ番号
(2)viエディタ

WEST-SECでは、設定を書き換える問題は、それほど多くありません。
ですが、古くからある標準のテキストエディタであるvi、または、改良版(iMproved)のvimを使えるようにしましょう。
以下のサイトに、基本的な操作がまとまっています。
eng-entrance.com
最低限の操作であれば、以下だけ覚えておきましょう。
・i で編集モードにする
・ESCで編集モードを抜ける --> ESCキーを押した後、Shift を押しながらzzで保存
             --> ESCキーを押した後、間違えて保存したくないなら :q! 
では、以下でエディタを開き、好きな文字を入れてみましょう。HTMLの構文にする必要はありません。

cd /var/www/html/team8/
vi index.html

http://IPアドレス/team8/index.html に接続すると、上記で入力した文字が表示されることでしょう。

(3)Webサーバのログ確認

・主催者から指定されたWebサーバに接続してください。
・自分のグローバルIPアドレスを調べます。
 ネットで「自分のIPアドレス」などで検索すると、調査してくれるサイトが表示されます。例)
https://www.cman.jp/network/support/go_access.cgi
・ログファイルを見てみましょう。
- CentOSの場合:/var/log/httpd/access_log
- Ubuntuの場合:/var/log/apache2/access.log
※事前にchmod o+r+x /var/log/httpd/ と chmod o+r+x /var/log/httpd/access_log を実行し、一般ユーザがログを閲覧できるようにしています。
・Logがたくさんありますね。これではどれが自分のログかがわかりません。そこで、grepを使って、自分のIPアドレスのログがあるかを確認しましょう。

cat /var/log/httpd/access_log | grep 203.113.0.132
(4)ログ解析のコマンド

ログは表示項目が大量にあり、そのまま解析するのは大変です。Excelで解析もいいのですが、Linux上でも整形できます。
いくつかの方法がありますが、awk(オーク)を使ってみましょう。awkに関しては、以下がまとまっています。
とほほのAWK入門 - とほほのWWW入門
①表示したい行を限定

cat /var/log/httpd/access_log | awk '{print $1}'

※複数表示する場合は、'{print $1,$2}' という感じで。
※awk -F "," とすることで、区切り文字をカンマ(,)に指定できます。
(2)数を数える
uniqで重複を排除、sortで並び替え、-cオプションで件数を数える

cat /var/log/httpd/access_log | awk '{print $1}' | sort | uniq -c

さらに、この結果を件数が多い順に並び替えるには、以下。※2回sortしないとカウントされないものがあります。

cat /var/log/httpd/access_log | awk '{print $1}' | sort | uniq -c | sort -n -r

3.nginx

nginx(エンジンエックス)は軽量のWebサーバ

(1)設定の確認
#バージョンの確認
nginx -v     # ==> nginx version: nginx/1.22.1

#サービスが起動しているかの確認
systemctl status nginx

ブラウザを立ち上げ、http://(サーバのIPアドレス)を指定し、Webサーバにアクセスできるかを確認。※httpsではないので注意。
「Welcome to nginx!」の文字が見えるはず。

(2)Webページの作成、該当ページへのアクセス

次は自分専用のページを作りましょう。
以下のフォルダがnginxのコンテンツの置き場所(デフォルト)。
/usr/share/nginx/html

・デフォルトのページを見てみよう。

#該当フォルダへ移動
$ cd /usr/share/nginx/html

#index.htmlを表示する
$ cat index.html

すると、先ほど見た「Welcome to nginx!」と記載されたページの内容が書かれてあるはず。

・自分のユーザ名のフォルダに移動してファイルを作成する。

#たとえば、user34であれば
cd /usr/share/nginx/html/user/user34

#viまたはvimでファイルを作成。拡張子は.htmlとしましょう。
vim a.html

#単に文字を打つだけなら、以下でも可能
echo "hello" >/usr/share/nginx/html/user/user34/a.html

・以下にアクセスし、自分が作ったファイルが作成されていることを確認しましょう。
http://(サーバのIPアドレス)/user/user34/a.html

文字化けするときは、以下を入れてください。

<meta http-equiv="content-type" charset="UTF-8">

たとえば、以下のようになります。

<html>
<meta http-equiv="content-type" charset="UTF-8">
こんにちは!
</html>
(3)設定ファイルの確認

nginxの設定ファイル(/etc/nginx/nginx.conf)を見て、このWebサーバの待ち受けポートや、コンテンツファイルやログのpathを確認しましょう。

(4)ログの確認

先ほどの設定ファイルに記載があったように、ログは以下です。
/var/log/nginx/access.log
tailまたはlessでログを見ましょう。

#lessでファイルを見る。抜ける場合は「q」。
less -f /var/log/nginx/access.log  

#lessでファイルを見る。必要に応じて、自分のIPアドレスでgrepすると、自分のログだけ見えます。
less -f /var/log/nginx/access.log | grep 203.0.113.231 

#tail-f でリアルタイムログ。以下を実行し、再度Webページにアクセスすると、ログが増える。抜ける場合は「ctl」+「c」
tail -f /var/log/nginx/access.log

・ログには何が書いてありますか?中身を全て確認しておきましょう。
http://nginx.org/en/docs/http/ngx_http_log_module.html
最後のuser-agentは、PCとスマホで違いを確認しましょう。

・以下を参考にして、user-agentごとに何件のアクセスがあったかを確認してください。
https://west-sec.com/linux_command#4%E3%83%AD%E3%82%B0%E8%A7%A3%E6%9E%90%E3%81%AE%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89
ヒントは以下をドラッグ
区切り文字として"を指定するとわかりやすいです。 -F '"'

4.確認コマンド

(1)ユーザのログイン関連の確認コマンド

・last:ログイン履歴
  last ユーザ名か、| grepで自分のユーザを指定し、ログイン時刻を確認しましょう。

last user1

・lastb:ログイン失敗履歴
  /var/log/btmpファイルは一般ユーザには権限がないので、好ましい設定ではないですが、 chmod o+r /var/log/btmp を実行しています。
  みると、大量の不正アクセスが確認できる。

lastb

・who:現在ログインしているユーザの一覧
  whoコマンドは、現在ログインしているユーザが、いつ、どこからログインしたかを表示する。直接ログインしている場合はtty(teletypewriter)と表示され、リモートなどから接続する場合はpts(pseudo terminal slave)と表示されます。

who

・/var/log/secure:ログイン成功/ログイン失敗
 ログインだけでなく、パスワード変更、sudoコマンド実行などの履歴が表示される。※このファイルが存在しない場合には、yum -y install rsyslog systemctl restart rsyslog.service を実行しましょう。
  | grep で自分のユーザを指定し、履歴を確認しましょう。

cat /var/log/secure
(2)プロセスやネットワーク

❶ps プロセスの確認コマンド
オプションにl(エル)を付けると、親プロセスの番号も表示される。

# ps l
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0  3197     1  20   0 120956  2092 -      Ss+  ttyS0      0:00 /sbin/agett
4     0  3306  3283  20   0 239816  7148 -      S    pts/0      0:00 sudo -i
4     0  3307  3306  20   0 124868  4284 -      S+   pts/0      0:00 -bash
4     0  5643  5354  20   0 158100  2164 -      R+   pts/1      0:00 ps l

❷通信の確認コマンド
netstatがあるが、今はssに置き換わっているであろう。

# netstat -tn
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 172.31.88.234:22        x.x.234.128:32726   ESTABLISHED
tcp        0      0 172.31.88.234:22        x.x.88.176:50180    SYN_RECV
tcp        0      1 172.31.88.234:40716     x.x.255.9:443         SYN_SENT
tcp        0    236 172.31.88.234:22        x.x.234.128:65490   ESTABLISHED
tcp        0      0 172.31.88.234:22        x.x.95.70:35352     SYN_RECV

自分のグローバルIPアドレスを以下で調べよう。SSHで接続しているはずなので、そのIPアドレスとの通信が確認できるはずである。
IPアドレス確認:自分のグローバルIPアドレスを確認 | NordVPN

# netstat -tn | grep 203.0.113.239
tcp        0     52 172.31.32.203:22        203.0.113.239:9168    ESTABLISHED

5.PHP

(1)インストール

・PHP(Personal Home Page Tools)は、Webページで簡単にプログラムを書くことができる言語です。
・インストール

yum install -y php
##私は以下でやっていました。
yum install -y php php-devel php-mysql php-gd php-mbstring

・バージョンの確認をしてみよう。

php -v
(2)簡単なページの作成

・初歩の初歩として、簡単なページを動かしてみましょう。書き方などは、以下が参考になります。
PHPの基礎の基礎|PHP工房
・/var/www/html/teamXの配下にtest.phpなどの名前で作成します。

cd /var/www/html/team3/  <--自分のユーザ番号のディレクトリに移動
vi test.php

たとえば、こんなソースコードです。インターネットからアクセスしてみましょう。
URLは、以下です。IPアドレスは指定されたもの。userXのXは各自の数字に変えてください。
http://IPアドレス/teamX/test.php
うまく表示されましたか?

<?php
echo 'run PHP';
?>

続いて、現在の時刻を表示してみよう。

<?php
echo 'run PHP'."<br>";
date_default_timezone_set('Asia/Tokyo');
echo date("Y/m/d H:i:s"); //大文字Yは4桁表記でyにすると2桁表記。Hは24時間単位で、hは12時間。
?>
(3)フォームを作ってみよう

・作成するページは以下の2つ

ファイル名 説明
input.html 数字を入力してもらう
number.php 入力された数字を表示する

・作り方
 - 入力してもらうには formタグを使う
 - method="post"としてください。
 - 渡された値をphpで表示するには、print($_REQUEST['number']); とする

では皆さん、頑張って作ってみてください。

【補足】nginxを使うなどして、文字化けするようであれば、以下を入れてください。

<meta http-equiv="content-type" charset="UTF-8">

■input.html

<html>
<meta http-equiv="content-type" charset="UTF-8">
好きな数字を半角で入力してください。
<form action="./number.php" method="post">
    <input type="text" name="number" value=""><br>
    <input type="submit" value="送信" >
</form>
</html>

■number.php

<html>
<meta http-equiv="content-type" charset="UTF-8">
あなたの好きな数字は、
<?php
print($_REQUEST['number']);
?>
です。
</html>
(4)GETとPOSTというメソッド違いを確認

method="get"とmethod="post"でどんな違いがあるかを確認しよう。
URLとWebサーバのログを確認しましょう。
- CentOSの場合:/var/log/httpd/access_log
- Ubuntuの場合:/var/log/apache2/access.log

(5)演算してみよう

・数字を2つ入力させて、その掛け算結果を表示してみよう。その際、(int)をつけて、文字を数字に型変換(キャスト)しましょう。
・送信の表示を「掛け算」にすると、皆さん手作りの電卓アプリっぽいですね。

■input.html

<html>
好きな数字を2つ、半角で入力してください。
<form action="./number.php" method="post">
   数字1<input type="text" name="number1" value=""><br>
   数字2<input type="text" name="number2" value=""><br>
    <input type="submit" value="掛け算" >
</form>
</html>

■number.php

<html>
あなたの好きな2つの数字を掛け算すると、
<?php
print((int)$_REQUEST['number1']*(int)$_REQUEST['number2']);
?>
です。
</html>
(6)【参考】PHPの条件分岐(switch)

PHPのプログラム体験として、switchによる簡単な条件分岐です。
以下を実行してみましょう。2行目にある、数字を変えると、表示される結果が変わります。

<?php
$number = 2; //ここで数字を設定

switch ($number) {
    case 1:
        echo '1です';
        break;
    case 2:
        echo '2です';
        break;
    case 3:
        echo '3です';
        break;
    default:
        echo '不明な値です';
        break;
}
?>
(7)エラーが出る場合

エラーとなる場合、その理由がわからないことがほとんどです。
そこで、ソースコードに以下を入れてみましょう。

<?php
error_reporting(E_ALL);  // 対象は全てのエラー
ini_set('display_errors', 1);  // エラーを出力。1の代わりに、 'On'としてもよい
?>

出力されたエラーに関して、GoogleやChatGPTに聞いてみるといいでしょう。

6.脆弱性を突いた攻撃

(1)OSコマンドインジェクションの脆弱性

❶OSコマンドインジェクションとは
安全なウェブサイトの作り方 - 1.2 OSコマンド・インジェクション | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構

❷実際にやってみよう

このWebページのプログラムは、IPアドレスを入れると、このWebサーバから通信テストを実施するものです。以下のように、8.8.8.8というIPアドレスを入れると、サーバから応答があることがわかります。

以下のphpファイルを作ってください。コピペで動くと思います。
■ping.php

<!DOCTYPE html>
<html lang="ja">

<body style="text-align:center;">
    <h1>Ping</h1>
    <?php
    print "Enter IP address<br>\n";
    ?>
    <form method="post">
        <input type="text" size="30" name="ipaddress" placeholder="127.0.0.1"><br>
        <input type="submit" value="check">
    </form>

    <?php
    if(isset($_POST['ipaddress'])){
                $ipaddress = $_POST["ipaddress"];
                $output = null;
                $retval = null;
                exec("ping -c 1 $ipaddress", $output, $retval);
                $output = nl2br(mb_convert_encoding(implode("\n", $output), "utf-8", "auto"));
                echo $output;
        }
    ?>

</body>
</html>

ソースコードの以下の部分がポイントです。攻撃者が、悪意をもって任意のOSコマンドを実行させるには、何を入れたらいいか、考えてみてください。

exec("ping -c 1 $ipaddress", $output, $retval);

攻撃1:whoコマンドを実行する →現在ログインしているユーザの一覧が表示
攻撃2:/etc/passwdファイルを表示する

(2)応用

もし、ソースコードが以下だったら、どういう値を入れればいいでしょうか。

exec("ping -c 1 '$ipaddress'", $output, $retval);
(3)ディレクトリトラバーサルの脆弱性

あまりかっこいいプログラムではありませんが、サンプルを作りました。
❶動作概要
・3つのファイルを、/var/www/html の直下に配置します。
・file.phpにアクセスします。URLとしては、http://xxxx/file.php になるでしょう。
・メニューとして、「ファイル一覧」「ファイル表示」の2つがあります。
・「ファイル一覧」をクリックすると、/var/www/html の配下にあるファイルが表示されます。
・「ファイル表示」において、file.php と入れてください。すると、file.php のソースコードが表示されるようになっています。

・参考ですが、今回のメニュー表示には、先に学習したswitchを使っています。

❷ソースファイル
file.php

<html>
<body>
<div style="float:left; width: 20%;">
<h1>メニュー</h1>
<ul>
<li><a href="file.php?action=list">ファイル一覧</a></li>
<li><a href="file.php?action=display">ファイル表示</a></li>
</div>
<div style="float:right; width: 80%;">
    <?php
    // メニュー項目ごとの処理
    if (isset($_GET['action'])) {   // issetは、変数がセット(set)されているかを確認する関数。isset($_GET['action'])は、GETリクエスト の中にactionというパラメータが含まれているかを確認する。
        $action = $_GET['action'];
        switch ($action) {
            case 'list':
                include('/var/www/html/list.php');
                break;
            case 'display':
                include('/var/www/html/display.php');
                break;
            default:
                echo '不明な操作';
                break;
        }
    }
    ?>
</div>
</body>
</html>

・list.php

<h1>ファイル一覧</h1>
<ul>
<?php
    $fileList = scandir('/var/www/html/');
    foreach ($fileList as $file) {
        if ($file != "." && $file != "..") {
            echo "<li>$file</li>";
        }
    }
?>
</ul>

・display.php

<h1>ファイル内容表示</h1>
<form action="" method="post">
    <label for="file_name">ファイル名:</label>
    <input type="text" name="file_name" id="file_name" required>
    <input type="submit" value="ファイル内容表示">
</form>
<?php
if (isset($_POST['file_name'])) {
    $fileName = $_POST['file_name'];
    $filePath = '/var/www/html/' . $fileName;
    // ファイルの存在を確認
    if (file_exists($filePath)) {
        echo '<h3>ファイル' . htmlspecialchars($fileName, ENT_QUOTES, 'UTF-8') . 'のソースコード</h3>';
        echo '<pre>';
        // ファイルの内容を表示
        echo htmlspecialchars(file_get_contents($filePath), ENT_QUOTES, 'UTF-8');
        echo '</pre>';
    } else {
        echo 'ファイルが存在しません: ' . htmlspecialchars($filePath, ENT_QUOTES, 'UTF-8');
    }
}
?>

❸ディレクトリトラバーサルによる攻撃
では、このサーバの/etc/passwdファイルを表示してみましょう。

「ファイル表示」において、「../../../../../../../../etc/passwd」などと入れます。

/etc/passwdのファイルは、rootだけしか見えないファイルではなく、以下を見てもらうとわかるように、誰もがread権限を持っています。

# ls -la /etc | grep passwd
-rw-r--r--.  1 root root     1551 Sep 25 23:02 passwd

❹対策
対策はいくつかの方法があります。
一例として、サニタイズ処理をします。PHPの場合、basename関数を使用することで、パスがないファイル名のみを抽出します。

    $safeFileName = basename($fileName); // ファイル名のみを抽出
    $filePath = '/var/www/html/' . $safeFileName; 

以下、../ などの文字が削除されている様子がわかります。

7.C言語

(1)概要など

・かつて、プログラム言語を学ぶときの基本として多くの方が学習した言語です。最近は、javaやpythonを含め、他の言語が使われることが増えています。
・以下のサイトに基礎の基礎があります。
https://c-lang.sevendays-study.com/day1.html

(2)簡単なプログラムを作ってみよう

画面上に「hello」を出力するプログラムである hello.exe を作ります。
①hello.cというファイルを作ります。

#include<stdio.h>
int main(){
 printf("こんにちは\n");
}

②コンパイルして、hello.exeというファイルを出力

gcc -o hello.exe hello.c

③実行権限の付与

chmod +x hello.exe

④ファイルの実行

./hello.exe

⑤pingを打つプログラムを作ってみよう

#include<stdio.h>
int main () {
    system ( "ping 8.8.8.8" );
}

8.Python言語

(1)概要など

オブジェクト指向型の言語で、スクリプト言語です。CやJavaなどに比べてシンプルな構文になっていて、なおかつ軽量、高速で、非常に人気のプログラムです。
❶環境構築
・WindowsPCやLinux環境でも動作しますが、GoogleColab環境が手軽でグラフィカルな表示もできてお勧めです。
https://colab.research.google.com/?hl=ja
・Googleアカウントが無い場合、paiza.ioが便利です。
https://paiza.io/ja/projects/new?language=python3

❷Linuxの場合

# python3 --version
Python 3.7.15
# python3

これにより、Pythonを実行できます。

(2)とりあえず、動かしてみよう

以下、いくつか初歩的な操作の記載があります。
https://qiita.com/AI_Academy/items/b97b2178b4d10abe0adb

>>> 1+1
2
>>> a=1
>>> b=2
>>> a+b
3
(3)ループ処理をしてみよう

❶1~10までの和を求めよう
注意点は、インデントに意味があるという点です。困ったら、ツールの自動インデント機能を使いましょう。具体的には前の行でEnterを押します

total=0
for n in range(1,11): #nは1-10の値を取ります。
  total = total + n
print(total) #-->55

または、こんな風にも書けます。

print(sum(range(1,11)))

❷1~nまでの和が1830になるnを求めよう
以下をいれてみよう。このままでは動かないので、少し工夫してください。

  if total == 1830:
      break

正解は以下になります。マウスでドラッグしてください。※仕様上、インデントが消えてしまっていますので、ご注意を。
for n in range(100):
total = total + n
if total == 1830:
break
print(n)

❸パスワードを解析(ハッシュ値から元のパスワードを求める)
user32のパスワードを解析したいと思います。
パスワードはmd5によってハッシュ化(=暗号化)されていて、サーバを攻撃して得たハッシュ値は 52dcb810931e20f7aa2f49b3510d3805 です。
user32のパスワードは、文字「pass」と数字5桁を組み合わせた単純なものであることがわかっています。***を設定して、プログラムを完成させましょう。

import hashlib

hash='52dcb810931e20f7aa2f49b3510d3805'

for ***:
    data='pass'+str(i)
    calc = hashlib.md5(pwd.encode()).hexdigest()
    if calc == hash:
        ***
        break

正解は以下になります。マウスでドラッグしてください。※仕様上、インデントが消えてしまっていますので、ご注意を。
import hashlib
hash='52dcb810931e20f7aa2f49b3510d3805'
for i in range(100000):
data='pass'+str(i)
calc = hashlib.md5(data.encode()).hexdigest()
if calc == hash:
print(data)
break

(4)動かないときの対処

プログラムがエラーになって困ったら、自分で考える以外に、今はchatGPTに聞くって方法もあります!どこが間違っているかを指摘し、修正コードまで書いてくれます。

9.データベースとSQL言語

(1)基本操作

❶DBへの接続

mysql -u root -proot;
use test_db;

ユーザ名 root パスワード root で接続します。

❷基本操作(主にDB関連)
・全てのテーブル表示

mysql> show tables;

・テーブルの削除 ★実施してはダメ

mysql> drop table users;

・DBの削除 ★実施してはダメ

mysql> drop databese db1;

・全てのデータベースを表示

mysql> show databases;

・mysqlのパスワードポリシーがデフォルトでは、、数字、大文字、小文字、特殊記号を含め8文字以上なので、セキュリティポリシー変更

set global validate_password_length=4;
set global validate_password_policy=LOW;
(2)操作系のコマンド

❶Select文の実施

mysql> select * from sales;

少しやってみよう。salesテーブルから、以下の問題を実施ください。
Q1.売り上げ(sales)が1000(万円)の店(store)を表示してみよう
指定した条件にマッチしたレコードだけを取り出すにはWHERE句を使用します。

mysql> select * from sales where sales = 1000;

Q2.平均売上を求めよう?
各列の平均値はAVG句を使用することで求めることができます。

mysql> select AVG(sales) from sales ;

Q3.storeがosakaの伝票は、何件あるか数えよう。
Q4.storeが渋谷の売り上げの最大値は?
Q5.最もsalesの平均が少ないstoreは?また、その数は?

mysql> select count(*) from sales where store = 'osaka';
mysql> select MAX(sales) from sales where store = 'shibuya';
mysql> select store, avg(sales) from sales group by store;

❷行の追加

INSERT INTO `users` VALUES (1,'user1','Masakazu Akita','pass7755',18,'Aichi');

❸列(項目)を追加

ALTER TABLE users ADD age VARCHAR(20) NULL;
ALTER TABLE users ADD prefecture VARCHAR(20) NULL;

❹列(項目)を削除

ALTER TABLE users DROP COLUMN abcde;

❺行の削除

#1行の場合
DELETE FROM users WHERE id = 1;

#複数行の削除
DELETE FROM users WHERE id=2 OR id=3;
DELETE FROM users WHERE id IN(3,4,5);
DELETE FROM users WHERE id > 10;

❻値の変更
id=1の行において、is_adminの値を1に書き換えます。

UPDATE users SET is_admin=1 WHERE id=1;
(3)SQL操作とSQLインジェクション

SQLインジェクションですが、例えば、ユーザのログインで、以下のチェックをしているとします。
入力されたユーザIDとパスワードが一致するものがあるかを確認し、あれば、ログインできる。

SELECT user_id FROM users WHERE user_id='$user_id' AND passwd='$passwd'

例として、正規のIDとパスワードが入力されると、以下になります。

SELECT user_id FROM users WHERE user_id ='user1' AND passwd='pass1234';

以下は、パスワードがわからない場合です。

SELECT user_id,passwd FROM users WHERE user_id ='user1' or '1'='1' AND passwd='';

これでも同じ結果が出ます。
ということは、ユーザIDに以下を入れれば、パスワードは空欄でもログインできます。 

user1' or '1'='1