Chapterبەش 9 · Lectureوانە 9.7
Securing Data in PHP and MySQLپاراستنی دراوە لە PHP و MySQL
How attackers use SQL injection (with the real attacks ' OR '1'='1 and admin' -- ), and the six defences: prepared statements with MySQLi and PDO, input validation, escaping output, password_hash(), least privilege and safe error messages.
هێرشبەران چۆن بەکاردەهێنن (بە هێرشە ڕاستەقینەکانی ' OR '1'='1 و admin' -- ) و شەش بەرگرییەکە: بە MySQLi و PDO، نێوخستە، بێزیانکردنی دەرخستە، password_hash()، کەمترین مۆڵەت و پەیامی هەڵەی سەلامەت.
What you will learnچی فێر دەبیت
- Explain how SQL injection works and show the query text before and after an attack.
- Write prepared statements with MySQLi (
bind_param) and with PDO (execute). - Validate ids, emails and choices on the server, and escape output with
htmlspecialchars(). - Explain why
md5()/sha1()are hashes, not encryption, and store passwords withpassword_hash(). - Apply least privilege to database users and show safe error messages.
- ڕوونی بکەیتەوە چۆن کار دەکات و دەقی پرسەکە پێش و دوای هێرشێک پیشان بدەیت.
- بە MySQLi (
bind_param) و بە PDO (execute) بنووسیت. - id و ئیمەیڵ و هەڵبژاردنەکان لەسەر ڕاژەکار پشتڕاست بکەیتەوە و دەرخستە بە
htmlspecialchars()بێزیان بکەیت. - ڕوونی بکەیتەوە بۆچی
md5()/sha1()هاشن نەک شفرەکردن و تێپەڕەوشە بەpassword_hash()هەڵبگریت. - لەسەر بەکارهێنەرانی بنکەدراوە جێبەجێ بکەیت و پەیامی هەڵەی سەلامەت پیشان بدەیت.
Why database security mattersبۆچی پاراستنی بنکەدراوە گرنگە
Until now, our pages have trusted everything that arrives from a or a . That is fine on your own computer. On the real Web, anybody can type anything into your forms, and some people type things on purpose to break your site, read other people's data or delete it.
تا ئێستا لاپەڕەکانمان متمانەیان بە هەموو ئەو شتانە کردووە کە لە یان دێن. لەسەر کۆمپیوتەری خۆت ئەمە کێشە نییە. بەڵام لەسەر وێبی ڕاستەقینە هەر کەسێک دەتوانێت هەر شتێک لە فۆرمەکانتدا بنووسێت و هەندێک کەس بە ئەنقەست شتێک دەنووسن بۆ ئەوەی تۆڕگەکەت تێک بدەن، دراوەی خەڵکی تر بخوێننەوە یان بیسڕنەوە.
In the course book this lecture is called Securing the data in PHP and MySQL (SQL injection). It is the lecture that turns the pages you built in PHP and MySQL: insert and select into pages that are safe to put online. You will learn six defences. The picture shows where each one sits on the way from the to the and back.
لە کتێبی کۆرسەکەدا ناوی ئەم وانەیە پاراستنی دراوە لە PHP و MySQL ()ـە. ئەم وانەیە ئەو لاپەڕانەی لە PHP و MySQL: زیادکردن و هێنانەوە دروستت کردن، دەکات بە لاپەڕەی سەلامەت بۆ سەر ئینتەرنێت. شەش ڕێگای بەرگری فێر دەبیت. وێنەکە پیشان دەدات هەر یەکێکیان لە کوێی ڕێگای نێوان و .
Five rules to remember
پێنج یاسا بۆ لەبیرکردن
- Never trust input. Everything in
$_GET,$_POST,$_COOKIEand$_FILEScomes from the visitor and can be anything, even a hidden field. - Keep data and code apart. User data must never become part of the SQL text: use .
- Check, then use. Validate every value before you use it, and escape every value when you print it into HTML.
- Store as little as possible, and nothing readable that is secret. Passwords are stored only as
password_hash()results. - Give little power and say little. Use a database user with only the rights it needs, and show visitors short error messages.
- هەرگیز متمانە بە مەکە. هەموو شتێک لە
$_GETو$_POSTو$_COOKIEو$_FILESدا لە سەردانیکەرەوە دێت و دەتوانێت هەر شتێک بێت، تەنانەت خانەیەکی شاراوەش. - دراوە و کۆد لێک جیا بکەرەوە. دراوەی بەکارهێنەر هەرگیز نابێت ببێتە بەشێک لە دەقی SQL: بەکاربهێنە.
- سەرەتا بپشکنە، پاشان بەکاری بهێنە. هەموو نرخێک پێش بەکارهێنان پشتڕاست بکەرەوە و لە کاتی چاپکردنی لەناو HTMLـدا بێزیانی بکە.
- کەمترین شت هەڵبگرە و هیچ نهێنییەکی خوێنراوە هەڵمەگرە. تێپەڕەوشە تەنها وەک ئەنجامی
password_hash()هەڵدەگیرێت. - دەسەڵاتی کەم بدە و کەم بدوێ. بەکارهێنەرێکی بنکەدراوە بەکاربهێنە کە تەنها مافە پێویستەکانی هەبێت و پەیامی هەڵەی کورت بە سەردانیکەران پیشان بدە.
Think of a post-office clerk who fills in a money-transfer form for you. You tell her the name and the amount, and she writes them into the boxes. If you say "the amount is 10 dinars, and also give me everything in the safe", a good clerk writes that whole sentence into the amount box. It stays text in a box, and it is never obeyed as an order. A careless clerk who obeys every sentence is like a PHP script that pastes input into SQL.
کارمەندێکی پۆستە بهێنە بەرچاو کە فۆرمی ناردنی پارە بۆت پڕ دەکاتەوە. تۆ ناو و بڕی پارەکەی پێ دەڵێیت و ئەو لەناو خانەکاندا دەینووسێت. ئەگەر بڵێیت «بڕەکە ١٠ دینارە، هەروەها هەموو پارەی ناو سندوقەکەشم بدەرێ»، کارمەندێکی باش هەموو ئەو ڕستەیە لە خانەی بڕەکەدا دەنووسێت. وەک دەقێک لەناو خانەیەکدا دەمێنێتەوە و هەرگیز وەک فەرمان جێبەجێ ناکرێت. کارمەندێکی بێئاگا کە گوێ لە هەموو ڕستەیەک دەگرێت، وەک دەسنووسێکی PHP وایە کە دەلکێنێت بە SQLـەوە.
Where the analogy stops workingسنووری ئەم نموونەیە
A real clerk can think; a database cannot. It does exactly what the SQL text says, so the separation must be built into the code.
کارمەندی ڕاستەقینە بیر دەکاتەوە؛ بنکەدراوە ناتوانێت. ڕێک ئەوە دەکات کە دەقی SQLـەکە دەیڵێت، بۆیە دەبێت جیاکردنەوەکە لەناو کۆدەکەدا بنیات بنرێت.
SQL injection: when data becomes codeتێخزاندنی SQL: کاتێک دراوە دەبێت بە کۆد
An attack in which someone types SQL code into a form or URL. If the script puts that input straight into its query, the attacker's code runs and can show or delete data. stop it.
هێرشێکە کە تێیدا هێرشبەر کۆدی SQL لە یان دەنووسێت. ئەگەر کۆدەکە ئەو نووسینە ڕاستەوخۆ لەگەڵ تێکەڵ بکات، کۆدی هێرشبەرەکە جێبەجێ دەبێت و دەتوانێت دراوەکان ئاشکرا بکات یان بیانسڕێتەوە. ڕێگەی لێ دەگرن.
A PHP page talks to the database by sending it one piece of text: the SQL command. When the page builds that text by gluing in what the visitor typed, the database receives one long string and has no way to know which part the programmer wrote and which part the visitor wrote. If the visitor types SQL characters such as ' or --, they become part of the command. That is SQL injection: the visitor's data is injected into your code. The Cybersecurity course explains the attack in general: SQL injection: what it is.
لاپەڕەیەکی PHP بە ناردنی یەک پارچە دەق قسە لەگەڵ بنکەدراوە دەکات: فەرمانی SQL. کاتێک لاپەڕەکە ئەو دەقە بە لکاندنی نووسینی سەردانیکەر دروست دەکات، بنکەدراوەکە درێژ وەردەگرێت و هیچ ڕێگایەکی نییە بزانێت کام بەشی بەرنامەساز نووسیویەتی و کام بەشی سەردانیکەر. ئەگەر سەردانیکەر نووسەی SQL وەک ' یان -- بنووسێت، دەبنە بەشێک لە فەرمانەکە. ئەمە ـە: دراوەی سەردانیکەر دەخزێنرێتە ناو کۆدەکەتەوە. کۆرسی هێرشەکە بە گشتی ڕوون دەکاتەوە: تێخزاندنی SQL: چییە.
Every SQL example in Lecture 8, Lab 8 and the students' practical notes was built this way, with '$variable' inside the SQL string. The two attacks below were really run against copies of that code.
هەموو نموونەیەکی SQL لە وانەی ٨ و تاقیگەی ٨ و تێبینییە پراکتیکییەکانی قوتابیاندا بەم شێوەیە دروستکراوە، بە '$variable' لەناو زنجیرەی SQLـەکەدا. دوو هێرشەکەی خوارەوە بە ڕاستی لەسەر کۆپییەکی ئەو کۆدانە تاقیکرانەوە.
Shows the two classic attacks, OR '1'='1' and the comment trick, on a practice site, then how to defend against them.
دوو هێرشە ناسراوەکە، OR '1'='1' و فێڵی ، لەسەر ڕاهێنان پیشان دەدات، پاشان چۆن بەرگرییان لێ دەکەیت.
Starts at 1:43, after the sponsor: OR and comment attacks, then defences
لە 1:43 دەست پێدەکات، دوای ڕیکلامەکە: هێرشی OR و هێرشی ، پاشان ڕێگاکانی بەرگری.
SQL injection: when data becomes codeتێخزاندنی SQL: کاتێک دراوە دەبێت بە کۆد
Attack 1: a condition that is always trueهێرشی ١: مەرجێک کە هەمیشە ڕاستە
Lecture 8 has a page called delete.php. The Delete link on the list page opens it as delete.php?id=3, and the page puts the id straight into a DELETE command. Below, the same code runs against a small practice table with three users. The attacker changes the address to delete.php?id=1' OR '1'='1.
وانەی ٨ لاپەڕەیەکی هەیە بە ناوی delete.php. بەستەری Delete لە لاپەڕەی لیستەکەدا وەک delete.php?id=3 دەیکاتەوە و لاپەڕەکە ناسنامەکە (id) ڕاستەوخۆ دەخاتە ناو فەرمانێکی DELETEـەوە. لە خوارەوە هەمان کۆد لەسەر خشتەیەکی ڕاهێنان بە سێ بەکارهێنەرەوە کار دەکات. هێرشبەرەکە ناونیشانەکە دەگۆڕێت بۆ delete.php?id=1' OR '1'='1.
<?php
// Practice database: an SQLite table in memory (no MySQL server needed)
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT)");
$pdo->exec("INSERT INTO users (firstname) VALUES ('Aram'), ('Shilan'), ('Karwan')");
// Pretend the visitor opened: delete.php?id=1' OR '1'='1
$_GET['id'] = "1' OR '1'='1";
$user_id = $_GET['id'];
$sql = "DELETE FROM users WHERE id='$user_id'"; // UNSAFE: input pasted into the SQL text
echo $sql, "\n";
$pdo->exec($sql);
echo "Rows left: ", $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(), "\n";DELETE FROM users WHERE id='1' OR '1'='1' Rows left: 0
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 3-5
A practice table with three rows, kept in memory by SQLite.
خشتەیەکی ڕاهێنان بە سێ ڕیزەوە، کە SQLite لە بیرگەدا هەڵیدەگرێت.
- Lineدێڕی 8
This is what PHP would receive from the address bar.
ئەمە ئەوەیە کە PHP لە وەری دەگرێت.
- Lineدێڕی 11
The dangerous line: the visitor's text becomes part of the SQL text.
دێڕە مەترسیدارەکە: نووسینی سەردانیکەر دەبێتە بەشێک لە دەقی SQL.
- Lineدێڕی 14
exec()runs the command. All rows match, so all rows are deleted.exec()فەرمانەکە جێبەجێ دەکات. هەموو ڕیزەکان دەگونجێن، بۆیە هەموویان دەسڕدرێنەوە.
Read the printed SQL carefully. The first ' that the attacker typed closes the quoted text early. What follows, OR '1'='1', is now SQL code. '1'='1' is true for every row, so WHERE matches every row and the table is emptied. When the course team ran the original delete.php this way, it printed "Record deleted successfully." and three users became zero.
SQLـە چاپکراوەکە بە وردی بخوێنەوە. یەکەم ' کە هێرشبەر نووسیویەتی، دەقە کۆتەیشنکراوەکە زوو دادەخات. ئەوەی دوای ئەو دێت، واتە OR '1'='1'، ئێستا کۆدی SQLـە. '1'='1' بۆ هەموو ڕیزێک ڕاستە، بۆیە WHERE لەگەڵ هەموو ڕیزێک دەگونجێت و خشتەکە بەتاڵ دەبێت. کاتێک delete.phpـە ڕەسەنەکە بەم شێوەیە تاقیکرایەوە، پەیامی «Record deleted successfully.»ـی چاپ کرد و سێ بەکارهێنەر بوون بە سفر.
The same trick works on any field, even a hidden one. In the lab's update.php the user's id travels in <input type="hidden" name="user_id">. Hidden only means "not shown on the screen": the visitor can still change it with the browser's developer tools. Sending user_id = 0' OR '1'='1 together with the first name HACKED changed the first name of every user to HACKED.
هەمان فێڵ لەسەر هەموو خانەیەک کار دەکات، تەنانەت خانەی شاراوەش. لە update.phpـی تاقیگەکەدا idـی بەکارهێنەر لەناو <input type="hidden" name="user_id">دا دەڕوات. شاراوە تەنها واتای «لەسەر شاشە پیشان نادرێت»ـە: سەردانیکەر هێشتا دەتوانێت بە تۆڕگەڕ بیگۆڕێت. ناردنی user_id = 0' OR '1'='1 لەگەڵ ناوی یەکەمی HACKED، ناوی یەکەمی هەموو بەکارهێنەرێکی کرد بە HACKED.
SQL injection: when data becomes codeتێخزاندنی SQL: کاتێک دراوە دەبێت بە کۆد
Attack 2: switch the rest off with a commentهێرشی ٢: کوژاندنەوەی بەشی دواتر بە لێدوان
A login page often checks the username and the password in one query. The practical notes contain exactly this pattern. In SQL, two dashes -- start a : everything after them on the line is ignored. The attacker types admin' -- (with a space at the end) as the username and anything as the password.
لاپەڕەی زۆر جار و تێپەڕەوشە لە یەک دەپشکنێت. تێبینییە پراکتیکییەکانی کۆرسەکە ڕێک ئەم شێوەیەیان تێدایە. لە SQLـدا دوو داش -- دەست پێدەکەن: هەموو شتێک دوای ئەوان لەسەر هەمان دێڕ پشتگوێ دەخرێت. هێرشبەرەکە admin' -- (بە بۆشاییەک لە کۆتاییدا) وەک دەنووسێت و هەر شتێک وەک تێپەڕەوشە.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)");
$pdo->exec("INSERT INTO users (username, password) VALUES ('admin', 'Xr7-secret'), ('hana', 'hana2026')");
// Pretend the login form was sent with these values
$_POST['user'] = "admin' -- ";
$_POST['pass'] = "I do not know it";
$username = $_POST['user'];
$password = $_POST['pass'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; // UNSAFE
echo $sql, "\n";
$row = $pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
echo $row ? "Logged in as: " . $row['username'] : "Login failed", "\n";SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'I do not know it' Logged in as: admin
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
Plain-text passwords are used here only to show the attack. Never store passwords like this.
تێپەڕەوشەی ئاشکرا لێرەدا تەنها بۆ پیشاندانی هێرشەکە بەکارهاتووە. هەرگیز تێپەڕەوشە بەم شێوەیە هەڵمەگرە.
- Lineدێڕی 7-8
The attacker's two fields.
دوو خانەکەی هێرشبەر.
- Lineدێڕی 12
Both values are glued into the SQL text.
هەردوو نرخەکە دەلکێنرێن بە دەقی SQLـەوە.
After admin' the quoted text is closed, and -- turns the rest of the line, including the password check, into a comment. The database runs SELECT * FROM users WHERE username = 'admin', finds the admin row, and the attacker is logged in without knowing the password. MySQL needs a space (or a tab or a new line) after --, which is why attackers type admin' -- with a space at the end. SQLite accepts it with or without the space.
دوای admin' دەقە کۆتەیشنکراوەکە دادەخرێت و -- باقی دێڕەکە، لەگەڵ پشکنینی تێپەڕەوشەکەدا، دەکات بە لێدوان. بنکەدراوەکە SELECT * FROM users WHERE username = 'admin' جێبەجێ دەکات، ڕیزی admin دەدۆزێتەوە و هێرشبەرەکە بەبێ زانینی تێپەڕەوشە دەچێتە ژوورەوە. MySQL پێویستی بە بۆشاییەک (یان tab یان دێڕێکی نوێ) دوای -- هەیە، بۆیە هێرشبەران admin' -- بە بۆشاییەکەوە لە کۆتاییدا دەنووسن. SQLite بە بۆشایی و بێ بۆشایی وەری دەگرێت.
SQL injection: when data becomes codeتێخزاندنی SQL: کاتێک دراوە دەبێت بە کۆد
Even honest visitors break itتەنانەت سەردانیکەری دڵسۆزیش تێکی دەدات
SQL injection is not only an attack. A normal name with an apostrophe, such as O'Brien, also closes the quoted text early. The lab's create.php failed in exactly this way.
تەنها هێرش نییە. ناوێکی ئاسایی کە نیشانەی 'ـی تێدایە، وەک O'Brien، ئەویش دەقە کۆتەیشنکراوەکە زوو دادەخات. create.phpـی تاقیگەکە ڕێک بەم شێوەیە شکستی هێنا.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT, lastname TEXT)");
$first_name = "Sara";
$last_name = "O'Brien"; // an honest name with an apostrophe
$sql = "INSERT INTO users (firstname, lastname) VALUES ('$first_name', '$last_name')";
echo $sql, "\n";
try {
$pdo->exec($sql);
echo "Saved.\n";
} catch (PDOException $e) {
echo "Error: ", $e->getMessage(), "\n";
}INSERT INTO users (firstname, lastname) VALUES ('Sara', 'O'Brien')
Error: SQLSTATE[HY000]: General error: 1 near "Brien": syntax errorIf a harmless name can change the meaning of your SQL, a harmful one can too. The fix is the same for both: send the name separately from the SQL, with a prepared statement.
ئەگەر ناوێکی بێزیان بتوانێت واتای SQLـەکەت بگۆڕێت، ناوێکی زیانبەخشیش دەتوانێت. چارەسەرەکە بۆ هەردووکیان یەکە: ناوەکە بە جیا لە SQLـەکە بنێرە، بە .
SQL injection: when data becomes codeتێخزاندنی SQL: کاتێک دراوە دەبێت بە کۆد
Common mistakes about SQL injectionهەڵە باوەکان دەربارەی تێخزاندنی SQL
- "It is a hidden field / a number in the URL, so it is safe." No. The visitor controls the whole request: hidden fields, the query string, cookies and headers.
- "I use POST, so nobody can change the data." No. POST hides data from the address bar, but anyone can send any POST request with a small script or the developer tools.
- "I run
htmlspecialchars()on the input, so SQL is safe." No.htmlspecialchars()protects HTML output, not SQL. Since PHP 8.1 its default settings happen to change'into', but that is an accident, and it also changes the data you store. - "Client-side validation (
required,type="email") stops bad input." No. It only helps honest users. An attacker does not use your form at all.
- «ئەمە خانەیەکی شاراوەیە / ژمارەیەکە لە URLـدا، کەواتە سەلامەتە.» نەخێر. سەردانیکەر هەموو کۆنترۆڵ دەکات: خانە شاراوەکان، ، و سەردێڕەکان.
- «POST بەکاردەهێنم، کەواتە کەس ناتوانێت دراوەکان بگۆڕێت.» نەخێر. POST دراوەکان لە دەشارێتەوە، بەڵام هەر کەسێک دەتوانێت بە دەسنووسێکی بچووک یان هەر داواکارییەکی POST بنێرێت.
- «
htmlspecialchars()لەسەر نێوخستەکە بەکاردەهێنم، کەواتە SQL سەلامەتە.» نەخێر.htmlspecialchars()دەرخستەی HTML دەپارێزێت، نەک SQL. لە PHP 8.1ـەوە، ڕێکخستنی بنەڕەتییەکەی بە ڕێکەوت'دەکات بە'، بەڵام ئەمە ڕێکەوتە و دراوەی هەڵگیراویش دەگۆڕێت. - « (
required،type="email") نێوخستەی خراپ ڕادەگرێت.» نەخێر. تەنها یارمەتی بەکارهێنەری دڵسۆز دەدات. هێرشبەر هەرگیز فۆرمەکەت بەکارناهێنێت.
The fix: prepared statementsچارەسەر: ڕستەی ئامادەکراو
An SQL statement sent to the database with placeholders (? or :name) instead of values. The values are sent separately, so is never run as SQL. This is the main defence against .
فەرمانێکی SQLـە کە بە شوێنگرەوە (? یان :name) لە جیاتی نرخەکان دەنێردرێت بۆ . نرخەکان بە جیا دەنێردرێن، بۆیە هەرگیز وەک SQL جێبەجێ ناکرێت. ئەمە سەرەکیترین ڕێگای بەرگرییە دژی .
With a prepared statement the SQL and the data travel separately. First PHP sends only the SQL, with a ? in each place where a value will go (the PHP manual calls it a parameter marker). The database reads it and fixes its plan. Then PHP sends the values. The database puts each value into its slot as plain data, so quotes, dashes and SQL words inside it are just characters. Why this is the strongest defence is explained in The fix: parameterised queries.
بە ، SQL و دراوە بە جیا دەڕۆن. یەکەم جار PHP تەنها SQLـەکە دەنێرێت، لەگەڵ ? لە هەر شوێنێک کە نرخێک دێتە ئەوێ. بنکەدراوەکە دەیخوێنێتەوە و پلانەکەی جێگیر دەکات. پاشان PHP نرخەکان دەنێرێت. بنکەدراوەکە هەر نرخێک وەک دراوەی ئاسایی دەخاتە شوێنەکەی خۆیەوە، بۆیە کۆتەیشن و داش و وشەی SQL لەناویدا تەنها نووسەن. بۆچی ئەمە بەهێزترین بەرگرییە، لە چارەسەر: پرسەکانی پارامیتەردار ڕوون کراوەتەوە.
A prepared statement is like a printed form at a government office. The form already says what will happen: "Transfer ____ dinars to account ____". You only fill in the blanks. Whatever you write in a blank, even "and close every other account", is read as the content of that blank. It cannot add a new line to the printed form.
وەک فۆرمێکی چاپکراوی فەرمانگەیەکی حکومی وایە. فۆرمەکە پێشتر دەڵێت چی ڕوودەدات: «____ دینار بگوازەرەوە بۆ هەژماری ____». تۆ تەنها بۆشاییەکان پڕ دەکەیتەوە. هەرچی لە بۆشاییەکدا بنووسیت، تەنانەت «و هەموو هەژمارەکانی تریش دابخە»، وەک ناوەڕۆکی ئەو بۆشاییە دەخوێنرێتەوە. ناتوانێت دێڕێکی نوێ بۆ فۆرمە چاپکراوەکە زیاد بکات.
Where the analogy stops workingسنووری ئەم نموونەیە
The form does not check that the blanks make sense. A prepared statement will happily store a wrong email address; checking that is the job of validation.
فۆرمەکە ناپشکنێت کە ئایا بۆشاییەکان واتادارن. بە ئاسانی ئیمەیڵێکی هەڵە هەڵدەگرێت؛ پشکنینی ئەوە کاری پشتڕاستکردنەوەیە.
The fix: prepared statementsچارەسەر: ڕستەی ئامادەکراو
Prepared statements with PDOڕستەی ئامادەکراو بە PDO
works with many databases, so we can run it here on SQLite. Write ? (or a name such as :last) where a value belongs, call prepare() once, then give the real values to execute() as an .
لەگەڵ زۆر بنکەدراوە کار دەکات، بۆیە دەتوانین لێرە لەسەر SQLite کاری پێ بکەین. لە شوێنی هەر نرخێک ? (یان ناوێک وەک :last) بنووسە، یەک جار prepare() بانگ بکە، پاشان نرخە ڕاستەقینەکان وەک بدە بە execute().
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT, lastname TEXT)");
// 1. INSERT with positional placeholders (?)
$stmt = $pdo->prepare("INSERT INTO users (firstname, lastname) VALUES (?, ?)");
$stmt->execute(["Sara", "O'Brien"]); // the apostrophe is now just a letter
$stmt->execute(["Aram", "Ahmed"]);
$stmt->execute(["Shilan", "Karim"]);
// 2. SELECT with a named placeholder (:last)
$stmt = $pdo->prepare("SELECT id, firstname FROM users WHERE lastname = :last");
$stmt->execute(['last' => "O'Brien"]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo "Found: ", $row['firstname'], " (id ", $row['id'], ")\n";
// 3. The attack string from before, now sent as data
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute(["1' OR '1'='1"]);
echo "Rows deleted: ", $stmt->rowCount(), "\n";
echo "Rows left: ", $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(), "\n";Found: Sara (id 1) Rows deleted: 0 Rows left: 3
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6-7
?marks a slot. The apostrophe in O'Brien is just a letter now.?شوێنێک نیشان دەدات. نیشانەی'لە O'Brien ئێستا تەنها پیتێکە.- Lineدێڕی 12-13
A named placeholder
:last; the array key'last'fills it.ناودار
:last؛ کلیلی'last'ی پڕی دەکاتەوە.- Lineدێڕی 18-19
The attack text from Attack 1 is only a value. No id equals that text, so nothing is deleted.
دەقی هێرشی ١ تەنها نرخێکە. هیچ idـیەک یەکسان نییە بەو دەقە، بۆیە هیچ ناسڕدرێتەوە.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)");
$pdo->exec("INSERT INTO users (username) VALUES ('admin'), ('hana')");
$username = "admin' -- "; // the same attack text as before
$stmt = $pdo->prepare("SELECT id, username FROM users WHERE username = ?");
$stmt->execute([$username]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
var_dump($row);bool(false)
No user is called exactly "admin' -- ", so no row is found.
هیچ بەکارهێنەرێک ناوی ڕێک «admin' -- » نییە، بۆیە هیچ ڕیزێک نادۆزرێتەوە.
The fix: prepared statementsچارەسەر: ڕستەی ئامادەکراو
Prepared statements with MySQLiڕستەی ئامادەکراو بە MySQLi
The course uses . Its prepared statements have four steps: prepare the SQL with ?, bind the variables with their types, execute, and read the result.
کۆرسەکە بەکاردەهێنێت. ئەو چوار هەنگاویان هەیە: SQLـەکە بە ? ئامادە بکە، لەگەڵ جۆرەکانیان ببەستەوە، جێبەجێی بکە و ئەنجامەکە بخوێنەوە.
<?php
require "config.php"; // gives us $conn (a mysqli connection)
$email = $_POST['email'] ?? '';
// 1. prepare: send the SQL with a ? where the value will go
$stmt = $conn->prepare("SELECT id, firstname, lastname FROM users WHERE email = ?");
// 2. bind: say what goes into each ? ("s" = string)
$stmt->bind_param("s", $email);
// 3. execute: now the value is sent, separately from the SQL
$stmt->execute();
// 4. read the rows
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo htmlspecialchars($row['firstname'] . " " . $row['lastname']), "<br>";
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
config.phpcreates$conn = new mysqli(...)(see the CRUD lecture).config.phpپەیوەندی$conn = new mysqli(...)دروست دەکات (سەیری وانەی CRUD بکە).- Lineدێڕی 10
"s"means one string. There must be one letter for each?."s"واتە یەک زنجیرەنووسە. بۆ هەر?ێک دەبێت یەک پیت هەبێت.- Lineدێڕی 16-19
get_result()gives a normal result; we escape the names before printing them.get_result()ئەنجامێکی ئاسایی دەدات؛ ناوەکان پێش چاپکردن بێزیان دەکەین.
| Letterپیت | Type of the valueجۆری نرخەکە | Exampleنموونە |
|---|---|---|
s | string (text) (دەق) | bind_param("s", $email) |
i | integer | bind_param("i", $user_id) |
d | double (a number with a decimal point)double، واتە | bind_param("d", $price) |
b | blob (binary data, sent in parts)blob (دراوەی دوانی، بە بەش دەنێردرێت) | bind_param("b", $null) |
The same steps in the procedural style, which the course also uses (mysqli_connect, mysqli_query):
هەمان هەنگاوەکان بە شێوازی پرۆسیجەری، کە کۆرسەکەش بەکاری دەهێنێت (mysqli_connect، mysqli_query):
<?php
require "config.php"; // gives us $conn
$user_id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if ($user_id !== false && $user_id !== null) {
$stmt = mysqli_prepare($conn, "DELETE FROM users WHERE id = ?");
mysqli_stmt_bind_param($stmt, "i", $user_id); // "i" = integer
mysqli_stmt_execute($stmt);
echo mysqli_stmt_affected_rows($stmt), " row(s) deleted.";
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
Validate first: only a whole number is accepted (see the next section).
سەرەتا پشتڕاستی بکەرەوە: تەنها وەردەگیرێت (بەشی داهاتوو ببینە).
- Lineدێڕی 7
"i": the value is bound as an integer."i": نرخەکە وەک دەبەسترێتەوە.- Lineدێڕی 9
How many rows were really deleted (0 if the id did not exist).
چەند ڕیز بە ڕاستی سڕدرانەوە (0 ئەگەر ئەو idـیە نەبوو).
<?php
require "config.php";
// PHP 8.1 and newer: give the values straight to execute()
$stmt = $conn->prepare("SELECT id, firstname FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->get_result()->fetch_assoc();
// PHP 8.2 and newer: prepare + bind + execute in one call
$result = $conn->execute_query("SELECT id, firstname FROM users WHERE firstname = ?", [$first_name]);Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6
Since PHP 8.1,
execute()accepts the values as an array. They are all sent as strings.لە PHP 8.1ـەوە
execute()نرخەکان وەک ڕیزەک وەردەگرێت. هەموویان وەک زنجیرەنووسە دەنێردرێن.- Lineدێڕی 10
Since PHP 8.2,
execute_query()does prepare, bind and execute in one call.لە PHP 8.2ـەوە
execute_query()ئامادەکردن و بەستنەوە و جێبەجێکردن بە یەک بانگکردن دەکات.
The fix: prepared statementsچارەسەر: ڕستەی ئامادەکراو
What a ? cannot stand forئەوەی ? ناتوانێت لە جێی بێت
A placeholder such as ? can stand only for a value: a number, a text, a date. It cannot stand for a table name, a column name or a keyword such as ASC/DESC. When such a part must come from the visitor (for example "sort by email"), compare it with an and use only names from your own list.
وەک ? تەنها دەتوانێت لە جێی نرخێک بێت: ژمارەیەک، دەقێک، بەروارێک. ناتوانێت لە جێی ناوی خشتە، ناوی ستوون یان وەک ASC/DESC بێت. کاتێک بەشێکی لەو جۆرە دەبێت لە سەردانیکەرەوە بێت (بۆ نموونە «بەپێی email ڕیز بکە»)، بەراوردی بکە لەگەڵ و تەنها ناوەکانی لیستەکەی خۆت بەکاربهێنە.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT, email TEXT)");
$pdo->exec("INSERT INTO users (firstname, email) VALUES ('Shilan', 'a@x.com'), ('Aram', 'c@x.com'), ('Karwan', 'b@x.com')");
function sorted_names(PDO $pdo, string $sort): array {
$allowed = ['firstname', 'email']; // the only column names we accept
if (!in_array($sort, $allowed, true)) {
$sort = 'firstname'; // anything else: use a safe default
}
return $pdo->query("SELECT firstname FROM users ORDER BY $sort")->fetchAll(PDO::FETCH_COLUMN);
}
echo implode(", ", sorted_names($pdo, "email")), "\n";
echo implode(", ", sorted_names($pdo, "email; DROP TABLE users")), "\n";
echo "Table still there: ", $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(), " rows\n";Shilan, Karwan, Aram Aram, Karwan, Shilan Table still there: 3 rows
Common mistakes with prepared statements
هەڵە باوەکانی ڕستەی ئامادەکراو
- Preparing a string that already contains the variable:
$conn->prepare("... WHERE id = $id")is exactly as unsafe asquery(). The variable must not appear in the SQL text at all. - Quotes around the
?:WHERE name = '?'searches for the text "?". WriteWHERE name = ?with no quotes. - A different number of
?and values:bind_param("ss", $a)stops with an error. Count them. %written in the SQL for LIKE: put the%signs into the value:execute(['%' . $q . '%'])withLIKE ?.
- ئامادەکردنی دەقێک کە گۆڕاوەکەی تێدایە:
$conn->prepare("... WHERE id = $id")ڕێک وەکquery()نەپارێزراوە. گۆڕاوەکە هەرگیز نابێت لەناو دەقی SQLـدا دەربکەوێت. - کۆتەیشن بە دەوری
?ـەوە:WHERE name = '?'بەدوای دەقی «?»دا دەگەڕێت.WHERE name = ?بێ کۆتەیشن بنووسە. - ژمارەی جیاوازی
?و نرخەکان:bind_param("ss", $a)بە هەڵە ڕادەوەستێت. بیانژمێرە. - نووسینی
%لەناو SQLـدا بۆ LIKE: نیشانەکانی%بخە ناو نرخەکەوە:execute(['%' . $q . '%'])لەگەڵLIKE ?.
Validating inputپشتڕاستکردنەوەی نێوخستە
Checking that is complete and correct before using it, for example that a number is in range or that a required field is not empty. In web forms it must always be done on the server.
پشکنینی ئەوەی تەواو و دروستە پێش بەکارهێنانی، بۆ نموونە ئەوەی ژمارەیەک لە سنووری خۆیدایە یان خانەیەکی پێویست بەتاڵ نییە. لە فۆرمەکانی وێبدا هەمیشە دەبێت لەسەر ڕاژەکار بکرێت.
Prepared statements stop input from becoming SQL, but they do not check whether the input makes sense. Validation does that: before you use a value, check its type, its length, its format, or that it is one of the allowed choices. Validation must happen on the , in PHP. Checks in the browser are only a help for honest users.
ڕێگە دەگرن لەوەی نێوخستە ببێت بە SQL، بەڵام ناپشکنن ئایا نێوخستەکە واتادارە. ئەو کارە دەکات: پێش ئەوەی نرخێک بەکاربهێنیت، جۆرەکەی، درێژییەکەی، شێوەکەی، یان ئەوە بپشکنە کە یەکێکە لە هەڵبژاردنە ڕێگەپێدراوەکان. دەبێت لەسەر ڕووبدات، لە PHPـدا. پشکنین لە تۆڕگەڕدا تەنها یارمەتییەکە بۆ بەکارهێنەری دڵسۆز.
Ids: whole numbers only
id: تەنها ژمارەی تەواو
Almost every CRUD page receives an id. filter_var() with FILTER_VALIDATE_INT returns the number, or false when the text is not a clean whole number. A cast with (int) never fails: it quietly turns rubbish into a number, which hides the attack instead of rejecting it.
نزیکەی هەموو لاپەڕەیەکی CRUD idـیەک وەردەگرێت. filter_var() لەگەڵ FILTER_VALIDATE_INT ژمارەکە دەگەڕێنێتەوە، یان false کاتێک دەقەکە ژمارەیەکی تەواوی پاک نییە. گۆڕین بە (int) هەرگیز شکست ناهێنێت: بە بێدەنگی شتی بێواتا دەکات بە ژمارە، کە هێرشەکە دەشارێتەوە لە جیاتی ڕەتکردنەوەی.
FILTER_VALIDATE_INT compared with (int)بەراوردی FILTER_VALIDATE_INT لەگەڵ (int)validate-id.phpPHPRuns on the serverلەسەر ڕاژەکاری وێب کار دەکات<?php
$inputs = ["7", " 7 ", "07", "-3", "7abc", "1' OR '1'='1", ""];
foreach ($inputs as $in) {
$valid = filter_var($in, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
printf("%-16s filter_var: %-6s (int): %d\n", '"' . $in . '"', var_export($valid, true), (int) $in);
}"7" filter_var: 7 (int): 7 " 7 " filter_var: 7 (int): 7 "07" filter_var: false (int): 7 "-3" filter_var: false (int): -3 "7abc" filter_var: false (int): 7 "1' OR '1'='1" filter_var: false (int): 1 "" filter_var: false (int): 0
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
min_range => 1: ids start at 1, so 0 and negative numbers are rejected too.min_range => 1: id لە 1ـەوە دەست پێدەکات، بۆیە 0 و ژمارەی نەرێنیش ڕەت دەکرێنەوە.
Text fields: length, format and allowed values
خانەی دەق: درێژی، شێوە و نرخی ڕێگەپێدراو
<?php
function validate_user(array $in): array {
$errors = [];
$first = trim($in['firstname'] ?? '');
$email = trim($in['email'] ?? '');
$gender = $in['gender'] ?? '';
if ($first === '' || mb_strlen($first) > 50) {
$errors[] = "First name must be 1-50 characters.";
}
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$errors[] = "Email address is not valid.";
}
if (!in_array($gender, ['Male', 'Female'], true)) {
$errors[] = "Choose a gender.";
}
return $errors;
}
print_r(validate_user(['firstname' => 'شیلان', 'email' => 'shilan@example.com', 'gender' => 'Female']));
print_r(validate_user(['firstname' => ' ', 'email' => 'shilan@', 'gender' => "Male' OR '1'='1"]));
echo mb_strlen('شیلان'), " letters, ", strlen('شیلان'), " bytes\n";Array
(
)
Array
(
[0] => First name must be 1-50 characters.
[1] => Email address is not valid.
[2] => Choose a gender.
)
5 letters, 10 bytesExplain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4-6
?? ''gives an empty string when a field was not sent at all, so there is no "Undefined array key" warning.?? ''زنجیرەنووسەیەکی بەتاڵ دەدات کاتێک خانەیەک هەر نەنێردرابێت، بۆیە ئاگادارکردنەوەی «Undefined array key» نییە.- Lineدێڕی 8
mb_strlen()counts letters;strlen()counts bytes, and every Kurdish letter is 2 bytes.mb_strlen()پیتەکان دەژمێرێت؛strlen()بایتەکان و هەر پیتێکی کوردی 2 بایتە.- Lineدێڕی 14
An : only
MaleorFemaleis accepted, compared strictly (true).: تەنها
MaleیانFemaleوەردەگیرێت، بە بەراوردی توند (true).
Validating inputپشتڕاستکردنەوەی نێوخستە
A helper that does too much: user_input()کردارێکی یاریدەدەر کە زیاد لە پێویست دەکات: user_input()
A student's registration page in the practical notes cleans every value with this helper before using it. Many tutorials teach a similar function, so it is worth looking at closely.
لاپەڕەی خۆتۆمارکردنی قوتابییەک لە تێبینییە پراکتیکییەکاندا هەموو نرخێک بەم کردارە یاریدەدەرە پاک دەکاتەوە پێش بەکارهێنانی. زۆر وانەی ئینتەرنێتی کردارێکی لەم شێوەیە فێر دەکەن، بۆیە شایەنی سەیرکردنی وردە.
user_input() really does to a valueuser_input() بە ڕاستی چی بە نرخێک دەکاتuser-input-helper.phpPHPRuns on the serverلەسەر ڕاژەکاری وێب کار دەکات<?php
// The helper from a student's Register.php (practical notes)
function user_input($data)
{
$data = htmlspecialchars($data);
$data = stripslashes($data);
$data = strtolower($data);
$data = trim($data);
return $data;
}
$raw = ' Karwan<b>O\'Neil\x ';
echo $raw, "|\n";
echo user_input($raw), "|\n";
echo user_input(md5("secret")), "\n";
echo md5("secret"), "\n";Karwan<b>O'Neil\x | karwan<b>o'neilx| 5ebe2294ecd0e0f08eab7690d2a6ee69 5ebe2294ecd0e0f08eab7690d2a6ee69
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
Turns
<,>and'into HTML entities, before the value is even stored.<و>و'دەکات بە ، تەنانەت پێش هەڵگرتنی نرخەکە.- Lineدێڕی 6
Removes backslashes: a leftover from the "magic quotes" of PHP 5.3 and older.
هێڵە لارەکان لادەبات: پاشماوەی «magic quotes»ـی PHP 5.3 و کۆنتر.
- Lineدێڕی 7
Changes the user's text to lower case without asking.
بێ پرسیار نووسینی بەکارهێنەر دەکات بە پیتی بچووک.
- Lineدێڕی 8
Removes spaces at both ends: the only step that belongs here.
بۆشایی هەردوو سەر لادەبات: تاکە هەنگاوێک کە شوێنی ئێرەیە.
The stored name is now karwan<b>o'neilx: lower case, full of HTML entities and missing a character. If the page later escapes it again for output, the visitor sees <b> instead of <b>. And running the helper on an md5() result changes nothing, because a hash has no such characters.
ناوە هەڵگیراوەکە ئێستا karwan<b>o'neilxـە: بە پیتی بچووک، پڕ لە و نووسەیەکی کەمە. ئەگەر لاپەڕەکە دواتر دووبارە بۆ دەرخستە بێزیانی بکاتەوە، سەردانیکەر <b> دەبینێت لە جیاتی <b>. و بەکارهێنانی کردارەکە لەسەر ئەنجامی md5() هیچ ناگۆڕێت، چونکە هاش ئەو جۆرە نووسانەی تێدا نییە.
Escaping output (stopping XSS)بێزیانکردنی دەرخستە (ڕێگرتن لە XSS)
An attack in which someone sends as , and a page shows it without escaping it, so the script runs in other visitors' browsers. Escape output with htmlspecialchars() to stop it.
هێرشێکە کە تێیدا کەسێک کۆدی وەک دەنێرێت و لاپەڕەکە بەبێ گۆڕینی هێما تایبەتەکان پیشانی دەدات، بۆیە کۆدەکە لە تۆڕگەڕی سەردانیکەرانی تردا کارا دەبێت. بۆ ڕێگرتن لێی، دەرخستە بە htmlspecialchars() بگۆڕە.
Data that comes out of the database is also user input: somebody typed it earlier. If a page prints it into HTML unchanged, a stored <script> runs in every visitor's browser. When the course team created a user whose first name was <script>alert(1)</script>, the lab's view.php printed it raw: <td><script>alert(1)</script></td>. (XSS as an attack: Cross-site scripting (XSS), briefly.)
ئەو دراوەیەی لە بنکەدراوە دەردەچێت ئەویش : کەسێک پێشتر نووسیویەتی. ئەگەر لاپەڕەیەک بێ گۆڕین لەناو HTMLـدا چاپی بکات، <script>ێکی هەڵگیراو لە تۆڕگەڕی هەموو سەردانیکەرێکدا کار دەکات. کاتێک بەکارهێنەرێک بە ناوی یەکەمی <script>alert(1)</script> دروستکرا، view.phpـی تاقیگەکە بە خاوی چاپی کرد: <td><script>alert(1)</script></td>. ( وەک هێرش: XSS، بە کورتی.)
The fix is htmlspecialchars(), which turns the characters < > & " ' into . The browser shows them as text and never runs them. Wrap it in a short helper so you can use it everywhere:
چارەسەرەکە htmlspecialchars()ـە، کە نووسەکانی < > & " ' دەکات بە . تۆڕگەڕ وەک دەق پیشانیان دەدات و هەرگیز جێبەجێیان ناکات. بیخە ناو کردارێکی یاریدەدەری کورتەوە بۆ ئەوەی لە هەموو شوێنێک بەکاری بهێنیت:
<?php
function e($value) {
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
// Imagine these values came out of the database
$first_name = '<script>alert(1)</script>';
$nickname = 'Hana "the best"';
$kurdish = 'شیلان';
echo "<td>" . $first_name . "</td>\n"; // UNSAFE
echo "<td>" . e($first_name) . "</td>\n"; // SAFE
echo '<input name="nick" value="' . $nickname . '">' . "\n"; // UNSAFE: the " ends the attribute
echo '<input name="nick" value="' . e($nickname) . '">' . "\n"; // SAFE
echo "<td>" . e($kurdish) . "</td>\n"; // Kurdish text is not changedHTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<td><script>alert(1)</script></td>
<td><script>alert(1)</script></td>
<input name="nick" value="Hana "the best"">
<input name="nick" value="Hana "the best"">
<td>شیلان</td>This is the HTML that PHP sends. In a browser, line 1 would run the script; line 2 shows the text <script>alert(1)</script>; line 3 breaks the input; line 4 shows Hana "the best"; the Kurdish name is unchanged.
ئەمە ئەو HTMLـەیە کە PHP دەینێرێت. لە تۆڕگەڕدا، دێڕی ١ سکریپتەکە کارا دەکات؛ دێڕی ٢ دەقی <script>alert(1)</script> پیشان دەدات؛ دێڕی ٣ خانەکە تێک دەدات؛ دێڕی ٤ Hana "the best" پیشان دەدات؛ ناوە کوردییەکە ناگۆڕێت.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 3
ENT_QUOTESalso escapes', and'UTF-8'names the character set.ENT_QUOTESنیشانەی'ـیش بێزیان دەکات و'UTF-8'ناوی کۆمەڵەی پیتەکەیە.- Lineدێڕی 13
Inside an attribute, a
"would end the value early.لەناو خەسڵەتێکدا،
"نرخەکە زوو کۆتایی پێدەهێنێت.
| Where the value goesنرخەکە دەچێتە کوێ | Useبەکاربهێنە |
|---|---|
| Into an SQL commandناو فەرمانێکی SQL | a prepared statement (? + execute() / bind_param()) (? + execute() / bind_param()) |
| Into HTML text or an attributeناو دەقی HTML یان خەسڵەتێک | htmlspecialchars($v, ENT_QUOTES, 'UTF-8') |
Into a URL (for example ?q=)ناو URLێک (بۆ نموونە ?q=) | urlencode($v) |
| Into a file nameناو ناوی فایلێک | do not use it: make your own name, as in the exercise belowبەکاری مەهێنە: ناوێکی خۆت دروست بکە، وەک لە ڕاهێنانی خوارەوەدا |
Storing passwords safelyهەڵگرتنی سەلامەتی تێپەڕەوشە
If your database is ever stolen, the attacker should not be able to read the passwords. Many people use the same password on other sites, so a leak can hurt them far beyond your site.
ئەگەر ڕۆژێک بنکەدراوەکەت دزرا، نابێت هێرشبەر بتوانێت تێپەڕەوشەکان بخوێنێتەوە. زۆر کەس هەمان تێپەڕەوشە لە تۆڕگەکانی تریشدا بەکاردەهێنن، بۆیە دزەکردنێک دەتوانێت زۆر زیاتر لە تۆڕگەکەی تۆ زیانیان پێ بگەیەنێت.
Explains why plain text, encryption and simple hashes are wrong for passwords, and what adding a salt to the hash does.
ڕوون دەکاتەوە بۆچی دەقی ئاسایی، و هاشی سادە بۆ هەڵەن و زیادکردنی بۆ هاشەکە چی دەکات.
Whole video: plain text, encryption, simple hash, then salted hash
هەموو ڤیدیۆکە: دەقی ئاسایی، ، هاشی سادە، پاشان هاش لەگەڵ .
Storing passwords safelyهەڵگرتنی سەلامەتی تێپەڕەوشە
md5() and sha1(): hashing, not encryptionmd5() و sha1(): هاشکردن، نەک شفرەکردن
Lecture 7 and the course summary list two functions for passwords:
وانەی ٧ و پوختەی کۆرسەکە دوو کردار بۆ تێپەڕەوشە دەناسێنن:
md5(string): "encrypts a string" and "returns a 32 characters hexadecimal number".sha1(string): "encrypts a string" and "returns a 64 characters hexadecimal number".
md5(string): «زنجیرەنووسەیەک شفرە دەکات» و «ژمارەیەکی شازدەیی 32 پیتی دەگەڕێنێتەوە».sha1(string): «زنجیرەنووسەیەک شفرە دەکات» و «ژمارەیەکی شازدەیی 64 پیتی دەگەڕێنێتەوە».
<?php
$str = "Hello";
echo md5($str), "\n";
echo strlen(md5($str)), " characters\n";
echo sha1($str), "\n";
echo strlen(sha1($str)), " characters\n";
echo hash('sha256', $str), "\n";
echo strlen(hash('sha256', $str)), " characters\n";8b1a9953c4611296a827abf8c47804d7 32 characters f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0 40 characters 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969 64 characters
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-3
The example from Lecture 7, page 21, with straight quotes.
نموونەکەی وانەی ٧، لاپەڕە ٢١، بە کۆتەیشنی ڕاست.
- Lineدێڕی 6
SHA-1 gives 40 hex characters (160 bits).
SHA-1 چل پیتی شازدەیی دەدات (160 بیت).
- Lineدێڕی 8
64 hex characters is SHA-256, a different function.
64 پیتی شازدەیی SHA-256ـە، کردارێکی ترە.
A function that turns any input into a short value of fixed size, called a hash; the same input always gives the same hash. A uses a fast hash function to choose where each is stored. Security uses cryptographic hash functions such as SHA-256, whose hash cannot be turned back into the input; md5() and sha1() are too fast and weak for passwords.
هەر نێوخستەیەک دەگۆڕێت بۆ نرخێکی کورتی قەبارە جێگیر کە پێی دەوترێت هاش؛ هەمان نێوخستە هەمیشە هەمان هاش دەدات. کرداری هاشی خێرا بەکار دەهێنێت بۆ ئەوەی بڕیار بدات هەر لە کوێ هەڵبگیرێت. ئاسایش کرداری هاشی شفرەیی بەکار دەهێنێت، وەک SHA-256، کە هاشەکەی ناگەڕێتەوە بۆ نێوخستەکە؛ md5() و sha1() بۆ تێپەڕەوشە زۆر خێرا و لاوازن.
Turning readable data into a secret code that only someone with the right key can turn back. Unlike a , encryption can be reversed with the key.
گۆڕینی دراوەی خوێنراوە بۆ کۆدێکی نهێنی، بە شێوەیەک کە تەنها خاوەنی کلیلی دروست بتوانێت بیگەڕێنێتەوە بۆ دەقی ئاسایی. بە پێچەوانەی ، ئەم کارە بە کلیلەکە دەگەڕێتەوە.
Storing passwords safelyهەڵگرتنی سەلامەتی تێپەڕەوشە
Why md5() and sha1() are wrong for passwordsبۆچی md5() و sha1() بۆ تێپەڕەوشە هەڵەن
Hashing is the right idea for passwords: you never need the password back, you only need to check it. But md5() and sha1() are the wrong kind of hash for this job.
هاشکردن بیرۆکەی دروستە بۆ تێپەڕەوشە: هەرگیز پێویستت بە گەڕانەوەی تێپەڕەوشەکە نییە، تەنها دەتەوێت بیپشکنیت. بەڵام md5() و sha1() جۆرێکی هەڵەی هاشن بۆ ئەم کارە.
<?php
// Two different people who chose the same weak password
echo "Rezan: ", md5("123456"), "\n";
echo "Dilan: ", md5("123456"), "\n";
echo "Big file? ", md5(str_repeat("a", 1000000)), "\n"; // still 32 charactersRezan: e10adc3949ba59abbe56e057f20f883e Dilan: e10adc3949ba59abbe56e057f20f883e Big file? 7707d6ae4e027c70eea2a935c2296f21
- They are very fast. They were made to check large files quickly. An attacker who steals your table can try billions of guesses per second on a normal graphics card.
- No salt. The same password gives the same hash for every user and on every website. Lists of common passwords and their hashes are published online, so
e10adc3949ba59abbe56e057f20f883eis known to mean123456. - They are broken. Researchers can make two different inputs with the same MD5 or SHA-1 hash, so they are not used for security any more.
- زۆر خێران. بۆ پشکنینی خێرای فایلی گەورە دروستکراون. هێرشبەرێک کە خشتەکەت بدزێت دەتوانێت لە چرکەیەکدا ملیاران مەزەندە لەسەر ئاسایی تاقی بکاتەوە.
- خوێیان نییە. هەمان تێپەڕەوشە بۆ هەموو بەکارهێنەرێک و لە هەموو تۆڕگەیەکدا هەمان هاش دەدات. لیستی تێپەڕەوشە باوەکان و هاشەکانیان لەسەر ئینتەرنێت بڵاوکراونەتەوە، بۆیە زانراوە
e10adc3949ba59abbe56e057f20f883eواتە123456. - شکاون. توێژەران دەتوانن دوو نێوخستەی جیاواز دروست بکەن کە هەمان هاشی MD5 یان SHA-1یان هەبێت، بۆیە ئیتر بۆ ئاسایش بەکارناهێنرێن.
A random value that is mixed into a password before it is hashed, so that the same password gives a different hash each time. password_hash() adds a new salt by itself and stores it inside the hash.
نرخێکی هەڕەمەکییە کە پێش هاشکردن تێکەڵی تێپەڕەوشە دەکرێت، بۆ ئەوەی هەمان تێپەڕەوشە هەر جارە هاشێکی جیاواز بدات. password_hash() خۆی خوێیەکی نوێ زیاد دەکات و لەناو هاشەکەدا هەڵیدەگرێت.
Storing passwords safelyهەڵگرتنی سەلامەتی تێپەڕەوشە
The right way: password_hash() and password_verify()ڕێگای دروست: password_hash() و password_verify()
Storing a password as a slow, salted hash instead of the password itself. In PHP use password_hash() to save it and password_verify() to check it at login; never use md5() or sha1() for this.
خەزنکردنی تێپەڕەوشەیە وەک هاشێکی خاو کە نرخێکی هەڕەمەکی (salt)ی تێکەڵ کراوە، نەک خودی تێپەڕەوشەکە. لە PHPـدا password_hash() بۆ خەزنکردن و password_verify() بۆ پشکنین لە کاتی بەکار بهێنە؛ هەرگیز md5() یان sha1() بۆ ئەم کارە بەکار مەهێنە.
<?php
$password = "Shilan#2026";
$hash = password_hash($password, PASSWORD_DEFAULT);
echo $hash, "\n";
echo strlen($hash), " characters\n";
print_r(password_get_info($hash));
var_dump(password_verify("Shilan#2026", $hash)); // the right password
var_dump(password_verify("shilan#2026", $hash)); // one letter different
$hash2 = password_hash($password, PASSWORD_DEFAULT);
var_dump($hash === $hash2); // same password, different hash
var_dump(password_verify($password, $hash2)); // ...and both still verify$2y$12$PhuS81Nm7nrn0Wo1Nea.Huhz8697RFPgssmNdDkecjPK4rQyGQPd6
60 characters
Array
(
[algo] => 2y
[algoName] => bcrypt
[options] => Array
(
[cost] => 12
)
)
bool(true)
bool(false)
bool(false)
bool(true)The hash and the salt are random, so your first line will be different every time you run the code.
هاش و خوێ هەڕەمەکین، بۆیە دێڕی یەکەمی تۆ هەر جارێک کۆدەکە کارپێدەکەیت جیاواز دەبێت.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
PASSWORD_DEFAULTlets PHP choose the best algorithm (bcrypt today). It adds a new random salt by itself.PASSWORD_DEFAULTڕێگە بە PHP دەدات باشترین هەڵبژێرێت (ئەمڕۆ bcrypt). خۆی خوێیەکی هەڕەمەکیی نوێ زیاد دەکات.- Lineدێڕی 9-10
password_verify()hashes the typed password with the salt stored inside$hashand compares.password_verify()تێپەڕەوشە نووسراوەکە بە خوێی ناو$hashهاش دەکات و بەراوردی دەکات.- Lineدێڕی 12-14
A second hash of the same password is different, and still verifies.
هاشی دووەمی هەمان تێپەڕەوشە جیاوازە و هێشتا پشتڕاست دەبێتەوە.
- The hash is 60 characters long today, but it may grow when PHP chooses a newer algorithm. Use a
VARCHAR(255)column. $2y$means bcrypt and12is the cost: how slow the hash is on purpose. PHP 8.4 raised the default cost from 10 to 12, so on XAMPP with PHP 8.2 you will see$2y$10$.- The salt and the cost are stored inside the hash itself, so you need only one column. Why salts and slow hashes protect passwords: Salts: storing passwords safely and Slow on purpose.
- هاشەکە ئەمڕۆ 60 پیتە، بەڵام لەوانەیە درێژتر بێت کاتێک PHP نوێتر هەڵدەبژێرێت. ستوونێکی
VARCHAR(255)بەکاربهێنە. $2y$واتە bcrypt و12تێچووـەکەیە: هاشەکە بە ئەنقەست چەند خاوە. PHP 8.4 تێچووی بنەڕەتی لە 10ـەوە بەرز کردەوە بۆ 12، بۆیە لە XAMPP بە PHP 8.2$2y$10$دەبینیت.- خوێ و تێچوو لەناو خودی هاشەکەدا هەڵدەگیرێن، بۆیە تەنها پێویستت بە یەک ستوونە. بۆچی و هاشی خاو دەپارێزن: خوێ: هەڵگرتنی تێپەڕەوشەکان بە سەلامەتی و بە ئەنقەست خاو.
A complete, safe register and login
خۆتۆمارکردن و چوونەژوورەوەیەکی تەواو و سەلامەت
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT UNIQUE, password TEXT)");
function register(PDO $pdo, string $username, string $password): void {
$hash = password_hash($password, PASSWORD_DEFAULT); // store a hash, never the password
$stmt = $pdo->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$username, $hash]);
}
function login(PDO $pdo, string $username, string $password): string {
$stmt = $pdo->prepare("SELECT id, username, password FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
return "Welcome, " . $user['username'];
}
return "Username or password is wrong."; // the same message in both cases
}
register($pdo, "admin", "Xr7-secret");
register($pdo, "hana", "hana2026");
echo login($pdo, "hana", "hana2026"), "\n";
echo login($pdo, "hana", "HANA2026"), "\n";
echo login($pdo, "admin' -- ", "anything"), "\n";
echo login($pdo, "nobody", "hana2026"), "\n";
echo substr($pdo->query("SELECT password FROM users WHERE username = 'hana'")->fetchColumn(), 0, 7), "...\n";Welcome, hana Username or password is wrong. Username or password is wrong. Username or password is wrong. $2y$12$...
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 12
Look the user up by name only. Never put the password into the SQL.
بەکارهێنەرەکە تەنها بە ناو بدۆزەرەوە. هەرگیز تێپەڕەوشە مەخە ناو SQLـەوە.
- Lineدێڕی 15
Check the password in PHP with
password_verify().تێپەڕەوشەکە لە PHPـدا بە
password_verify()بپشکنە.- Lineدێڕی 18
The same message for a wrong name and a wrong password, so an attacker cannot learn which names exist.
هەمان پەیام بۆ ناوی هەڵە و تێپەڕەوشەی هەڵە، بۆ ئەوەی هێرشبەر نەزانێت کام ناوانە هەن.
Common mistakes with passwords
هەڵە باوەکانی تێپەڕەوشە
- Comparing in SQL:
WHERE password = '" . md5($pass) . "'needs the unsafe hash and, withpassword_hash(), cannot work at all because every hash is different. - Putting the stored hash into the password box of an edit form. If the user saves without typing, the page hashes the hash and the real password stops working. See the exercise in the CRUD lecture.
- A column that is too short:
VARCHAR(32)was made for md5 and cuts a bcrypt hash, so no login ever succeeds. - Showing or logging passwords: never print a password, even in a welcome message or an error log.
- بەراوردکردن لەناو SQL:
WHERE password = '" . md5($pass) . "'پێویستی بە هاشە نەپارێزراوەکەیە و، لەگەڵpassword_hash()، هەرگیز کار ناکات چونکە هەموو هاشێک جیاوازە. - دانانی هاشی هەڵگیراو لە فۆرمی دەستکاریکردن. ئەگەر بەکارهێنەر بێ نووسین پاشەکەوتی بکات، لاپەڕەکە هاشەکە هاش دەکاتەوە و تێپەڕەوشە ڕاستەقینەکە ئیتر کار ناکات. ڕاهێنانەکەی وانەی CRUD ببینە.
- ستوونێکی زۆر کورت:
VARCHAR(32)بۆ md5 دروستکرابوو و هاشی bcrypt دەبڕێت، بۆیە هیچ چوونەژوورەوەیەک سەرناکەوێت. - پیشاندان یان تۆمارکردنی تێپەڕەوشە: هەرگیز تێپەڕەوشە چاپ مەکە، تەنانەت لە پەیامی بەخێرهاتن یان لۆگی هەڵەکاندا.
How to create a function, send values in with arguments, and get a result back with return, with a short practice example.
چۆن دروست بکەیت، بە بنێریتە ناوی و بە return ئەنجامێک لێی وەربگریتەوە، لەگەڵ ڕاهێنانێکی کورت.
Watch 3:22:28–3:26:07: password_hash() and password_verify()
لە 3:22:28 تا 3:26:07 سەیر بکە: password_hash() و password_verify().
Least privilege: give only the power that is neededکەمترین مۆڵەت: تەنها ئەو دەسەڵاتە بدە کە پێویستە
Give every user, program and database account only the permissions it needs for its job, and nothing more. Then a mistake or an attack can do less damage.
هەر بەکارهێنەرێک، بەرنامەیەک و هەژمارێکی تەنها ئەو مۆڵەتانە وەربگرێت کە بۆ کارەکەی پێویستن، نەک زیاتر. ئەوکات هەڵەیەک یان هێرشێک زیانی کەمتر دەگەیەنێت.
A hotel gives you a key card that opens your own room and the front door, not every room and not the kitchen. If you lose the card, the damage is small. A database user works the same way.
هوتێلێک کارتێکی کلیلت دەداتێ کە ژوورەکەی خۆت و دەرگای سەرەکی دەکاتەوە، نەک هەموو ژوورێک و نەک چێشتخانەکە. ئەگەر کارتەکە ون بکەیت، زیانەکە کەمە. بەکارهێنەری بنکەدراوەش بە هەمان شێوە کار دەکات.
Where the analogy stops workingسنووری ئەم نموونەیە
A card is lost by accident; in an attack, someone uses your web page on purpose to do the most harm the database user allows.
کارت بە ڕێکەوت ون دەبێت؛ لە هێرشێکدا کەسێک بە ئەنقەست لاپەڕەکەت بەکاردەهێنێت بۆ زۆرترین زیان کە بەکارهێنەری بنکەدراوەکە ڕێگەی پێدەدات.
XAMPP's root user with an empty password can do everything: drop every database, create users and read every table. That is acceptable only on your own computer. On a real server, create a separate user for each web application and give it only the rights it needs, usually reading and changing rows in one database.
بەکارهێنەری rootـی XAMPP بە تێپەڕەوشەی بەتاڵەوە دەتوانێت هەموو شتێک بکات: هەموو بنکەدراوەیەک بسڕێتەوە، بەکارهێنەر دروست بکات و هەموو خشتەیەک بخوێنێتەوە. ئەمە تەنها لەسەر کۆمپیوتەری خۆت پەسەندە. لەسەر ڕاژەکارێکی ڕاستەقینە، بۆ هەر بەکارهێنەرێکی جیا دروست بکە و تەنها ئەو مافانەی بدەرێ کە پێویستیەتی، زۆربەی جار خوێندنەوە و گۆڕینی ڕیزەکانی یەک بنکەدراوە.
-- Run once as an administrator (for example in phpMyAdmin's SQL tab)
CREATE USER 'school_app'@'localhost' IDENTIFIED BY 'use-a-long-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON school.* TO 'school_app'@'localhost';
-- The web application cannot do these any more:
-- DROP TABLE, CREATE USER, GRANT, reading other databasesExplain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
'localhost': this user may connect only from the same computer as the database.'localhost': ئەم بەکارهێنەرە تەنها لە هەمان کۆمپیوتەری بنکەدراوەکەوە دەتوانێت پەیوەندی بکات.- Lineدێڕی 3
:
GRANTgives exactly four rights on theschooldatabase and nothing else.:
GRANTتەنها چوار ماف لەسەر بنکەدراوەیschoolدەدات و هیچی تر.
Then use this user in config.php. If an attacker ever finds an injection hole, they still cannot drop tables or reach other databases.
پاشان ئەم بەکارهێنەرە لە config.phpـدا بەکاربهێنە. ئەگەر هێرشبەرێک ڕۆژێک کونێکی تێخزاندن بدۆزێتەوە، هێشتا ناتوانێت خشتە بسڕێتەوە یان بگات بە بنکەدراوەکانی تر.
- Keep
config.phpand other secrets outside the public web folder when you can, and never show database passwords on a page. - Change the default passwords of admin tools such as and , or do not install them on a public server.
- Least privilege also applies to the people who use your site: check that the logged-in user is allowed to edit or delete a record. See Who may do what.
- ئەگەر دەتوانیت،
config.phpو نهێنییەکانی تر لە دەرەوەی فۆڵدەری گشتیی وێب دابنێ و هەرگیز تێپەڕەوشەی بنکەدراوە لەسەر لاپەڕەیەک پیشان مەدە. - ئامرازە بەڕێوەبەرییەکانی وەک و بگۆڕە، یان لەسەر ڕاژەکارێکی گشتی دایان مەمەزرێنە.
- کەمترین مۆڵەت بۆ ئەو کەسانەش دەگونجێت کە تۆڕگەکەت بەکاردەهێنن: بپشکنە ئایا بەکارهێنەری چووەژوورەوە ڕێگەی پێدراوە تۆمارێک دەستکاری بکات یان بیسڕێتەوە. کێ دەتوانێت چی بکات ببینە.
Safe error messagesپەیامی هەڵەی سەلامەت
The course's CRUD pages print "Error:" . $sql . "<br>" . $conn->error when a query fails, and the practical notes use die(mysqli_error($connection)). That shows the visitor your SQL, your table and column names and the database's own message: a free map for an attacker. Because $sql contains the raw input, it is also an XSS hole.
لاپەڕەکانی CRUDـی کۆرسەکە کاتێک پرسێک شکست دەهێنێت "Error:" . $sql . "<br>" . $conn->error چاپ دەکەن و تێبینییە پراکتیکییەکان die(mysqli_error($connection)) بەکاردەهێنن. ئەمە SQLـەکەت، ناوی خشتە و ستوونەکانت و پەیامی خودی بنکەدراوەکە بە سەردانیکەر پیشان دەدات: نەخشەیەکی بێبەرانبەر بۆ هێرشبەر. لەبەر ئەوەی $sql نێوخستە خاوەکەی تێدایە، کونێکی XSSـیشە.
Since PHP 8.1, MySQLi throws an (mysqli_sql_exception) when a query fails, and PDO has done so since PHP 8.0. So the else { echo "Error: ..." } branches in the course code never run any more. Catch the exception, write the details to the server's error log, and show the visitor a short, friendly message:
لە PHP 8.1ـەوە MySQLi کاتێک پرسێک شکست دەهێنێت (mysqli_sql_exception) فڕێ دەدات و PDO لە PHP 8.0ـەوە ئەم کارە دەکات. بۆیە لقەکانی else { echo "Error: ..." } لە کۆدی کۆرسەکەدا ئیتر هەرگیز کار ناکەن. هەڵاوێردەکە بگرە، وردەکارییەکان لە لۆگی هەڵەکانی ڕاژەکار بنووسە و پەیامێکی کورت و دۆستانە بە سەردانیکەر پیشان بدە:
<?php
$pdo = new PDO('sqlite::memory:'); // PDO throws exceptions by default since PHP 8.0
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT)");
try {
$rows = $pdo->query("SELECT * FROM userz")->fetchAll(); // a typo in the table name
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage()); // full details: for you, in the server log
echo "Sorry, something went wrong. Please try again later.\n"; // short message: for the visitor
}Sorry, something went wrong. Please try again later.
The visitor sees only this line. The server's error log receives: Database error: SQLSTATE[HY000]: General error: 1 no such table: userz
سەردانیکەر تەنها ئەم دێڕە دەبینێت. لۆگی هەڵەکانی ڕاژەکار ئەمە وەردەگرێت: Database error: SQLSTATE[HY000]: General error: 1 no such table: userz
; php.ini on a public server
display_errors = Off ; never print PHP errors into the page
log_errors = On ; write them to the error log insteadExplain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
On your own XAMPP, errors are usually shown on the page, which helps while you learn. Turn this off on a real server.
لەسەر XAMPPـی خۆت هەڵەکان زۆرجار لەسەر لاپەڕەکە پیشان دەدرێن، کە لە کاتی فێربووندا یارمەتیدەرە. لەسەر ڕاژەکاری ڕاستەقینە بیکوژێنەرەوە.
Old code you will meet: the mysql_ functionsکۆدی کۆن کە دەیبینیت: کردارەکانی mysql_
The course summary lists the "commonly used functions" of the old mysql extension. You will still find them in old tutorials and books, so you should recognise them, but they were removed in PHP 7.0 (2015). They also had no prepared statements, so code written with them is almost always open to SQL injection.
پوختەی کۆرسەکە «کردارە بەکارهاتووە باوەکانی» پاشکۆی کۆنی mysql دەژمێرێت. هێشتا لە وانە و کتێبە کۆنەکاندا دەیانبینیت، بۆیە دەبێت بیانناسیتەوە، بەڵام لە PHP 7.0دا لابران (٢٠١٥). هەروەها نەبوو، بۆیە ئەو کۆدەی پێیان نووسراوە نزیکەی هەمیشە ڕێگە بە دەدات.
| Old function (summary)کرداری کۆن (پوختە) | What the summary saysپوختەکە چی دەڵێت | Use today (MySQLi)ئەمڕۆ بەکاربهێنە (MySQLi) |
|---|---|---|
mysql_connect("hostname","user","pass") | connects to the database serverپەیوەندی بە ڕاژەکاری بنکەدراوەوە دەکات | new mysqli($host, $user, $pass, $db) |
mysql_select_db("database name", "handle") | selects the databaseبنکەدراوەکە هەڵدەبژێرێت | 4th argument of new mysqli(), or $conn->select_db($db) |
mysql_query() | runs the query; takes the SQL and the connection returned in step 1پرسەکە جێبەجێ دەکات؛ SQL و ئەو پەیوەندییە وەردەگرێت کە لە هەنگاوی ١دا گەڕاوەتەوە | $conn->prepare(...) for input, $conn->query($sql) only without input |
mysql_num_fields() | number of fields (columns) in a result setژمارەی ستوونەکانی | $result->field_count |
mysql_fetch_array($result) | one row as an associative array, a numeric array, or bothیەک ڕیز وەک ، ژمارەدار، یان هەردووکیان | $result->fetch_array() |
mysql_fetch_assoc($result) | one row as an associative arrayیەک ڕیز وەک | $result->fetch_assoc() |
mysql_error() | the error message from the MySQL serverپەیامی هەڵەی ڕاژەکاری MySQL | exceptions (try/catch); $conn->error |
md5(string) | "encrypts" into 32 hexadecimal characters«شفرە دەکات» بۆ 32 پیتی شازدەیی | hash, not encryption; not for passwords → password_hash()هاشە نەک شفرەکردن؛ نەک بۆ تێپەڕەوشە ← password_hash() |
sha1(string) | "encrypts" into 64 hexadecimal characters«شفرە دەکات» بۆ 64 پیتی شازدەیی | 40 characters, a hash; not for passwords → password_hash()40 پیتە، هاشە؛ نەک بۆ تێپەڕەوشە ← password_hash() |
<?php
var_dump(function_exists('mysql_connect'));
var_dump(function_exists('mysqli_connect'));
mysql_connect("localhost", "root", "");bool(false)
bool(true)
Fatal error: Uncaught Error: Call to undefined function mysql_connect() in …/old.php:4
Stack trace:
#0 {main}
thrown in …/old.php on line 4Real output from PHP 8.5; the long folder path is shortened to …
دەرخستەی ڕاستەقینەی PHP 8.5؛ ڕێڕەوی درێژی فۆڵدەرەکە بە … کورتکراوەتەوە.
Exercisesڕاهێنانەکان
Try each exercise yourself before you open the answer. The first group is about SQL injection, the second is a real student page full of problems, and the last group practises ids and passwords.
هەر ڕاهێنانێک خۆت هەوڵی بدە پێش ئەوەی وەڵامەکە بکەیتەوە. کۆمەڵەی یەکەم دەربارەی ، دووەمیان لاپەڕەیەکی ڕاستەقینەی قوتابییەکە پڕ لە کێشە و کۆمەڵەی کۆتایی ڕاهێنان لەسەر id و دەکات.
Exercisesڕاهێنانەکان
Exercises: SQL injectionڕاهێنان: تێخزاندنی SQL
This search page lists users whose first name contains the text from ?q=. Find the security problem and rewrite the query safely with PDO. Keep the LIKE search.
ئەم لاپەڕەی گەڕانە ئەو بەکارهێنەرانە پیشان دەدات کە ناوی یەکەمیان دەقەکەی ?q=ـی تێدایە. کێشە ئاسایشییەکە بدۆزەرەوە و پرسەکە بە PDO بە سەلامەتی دووبارە بنووسەوە. گەڕانی LIKE بهێڵەوە.
<?php
$q = $_GET['q'];
$sql = "SELECT firstname, email FROM users WHERE firstname LIKE '%$q%'";
foreach ($pdo->query($sql) as $row) {
echo $row['firstname'], " - ", $row['email'], "\n";
}Hintئاماژە
The % signs belong in the value. Also look at the echo line.
نیشانەکانی % دەچنە ناو نرخەکەوە. سەیری دێڕی echoـیش بکە.
Show answerوەڵامەکە پیشان بدە
Problems: $q is pasted into the SQL (SQL injection), $_GET['q'] may be missing (warning), and the output is not escaped (XSS). Answer, tested with three users:
کێشەکان: $q دەلکێنرێت بە SQLـەوە ()، لەوانەیە $_GET['q'] نەبێت (ئاگادارکردنەوە) و دەرخستەکە بێزیان نەکراوە (XSS). وەڵام، بە سێ بەکارهێنەر تاقیکراوەتەوە:
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT, email TEXT)");
$pdo->exec("INSERT INTO users (firstname, email) VALUES ('Aram', 'aram@example.com'), ('Rezan', 'rezan@example.com'), ('Hana', 'hana@example.com')");
$q = $_GET['q'] ?? "re"; // what the visitor typed in the search box
// The % signs go into the VALUE, not into the SQL text
$stmt = $pdo->prepare("SELECT firstname, email FROM users WHERE firstname LIKE ?");
$stmt->execute(['%' . $q . '%']);
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo htmlspecialchars($row['firstname']), " - ", htmlspecialchars($row['email']), "\n";
}
// The attack text is now only something to search for
$stmt->execute(['%' . "' OR '1'='1" . '%']);
echo "Rows for the attack text: ", count($stmt->fetchAll()), "\n";Rezan - rezan@example.com Rows for the attack text: 0
A login query is built like this: "SELECT * FROM users WHERE email='$email' AND password='$password'". Write the exact SQL text the database receives when the visitor types x' OR '1'='1' -- as the email and anything as the password. Which rows does it return?
پرسێکی چوونەژوورەوە بەم شێوەیە دروستکراوە: "SELECT * FROM users WHERE email='$email' AND password='$password'". ڕێک ئەو دەقی SQLـە بنووسە کە بنکەدراوەکە وەری دەگرێت کاتێک سەردانیکەر x' OR '1'='1' -- وەک ئیمەیڵ و anything وەک تێپەڕەوشە دەنووسێت. کام ڕیزانە دەگەڕێنێتەوە؟
Show answerوەڵامەکە پیشان بدە
SELECT * FROM users WHERE email='x' OR '1'='1' -- ' AND password='anything'
'1'='1' is always true and everything after -- is a comment, so the query returns every user, and a login page that takes the first row logs the attacker in as that user (often the admin).
'1'='1' هەمیشە ڕاستە و هەموو شتێک دوای -- لێدوانە، بۆیە پرسەکە هەموو بەکارهێنەران دەگەڕێنێتەوە و لاپەڕەیەکی چوونەژوورەوە کە یەکەم ڕیز وەردەگرێت هێرشبەرەکە وەک ئەو بەکارهێنەرە دەباتە ژوورەوە (زۆرجار بەڕێوەبەر).
Exercisesڕاهێنانەکان
Exercise: find the bugs in Register.phpڕاهێنان: باگەکانی Register.php بدۆزەرەوە
Find the security bugs: below is the student's Register.php from the practical notes (the config.php it includes is not shown and gives $connection). List every problem you can find, then write a fixed version with MySQLi prepared statements.
باگە ئاسایشییەکان بدۆزەرەوە: لە خوارەوە Register.phpـی قوتابییەکە لە تێبینییە پراکتیکییەکانەوەیە (ئەو config.phpـەی هاوردەی دەکات پیشان نەدراوە و $connection دەدات). هەموو کێشەیەک کە دەیدۆزیتەوە بنووسە، پاشان وەشانێکی چاککراو بە MySQLi بنووسە.
<?php
include("config.php");
function user_input($data)
{
$data = htmlspecialchars($data);
$data = stripslashes($data);
$data = strtolower($data);
$data = trim($data);
return $data;
}
if (isset($_POST['Register'])) {
$username = user_input($_POST['UserInput']);
$selectQuery = mysqli_query($connection, "select username from users where username ='$username'");
if (mysqli_num_rows($selectQuery)) {
while (mysqli_fetch_assoc($selectQuery)) {
echo "username already exist";
}
} else {
$file_name = $_FILES['FileInput']['name'];
$file_tmp = $_FILES['FileInput']['tmp_name'];
$ext1 = explode(".", $file_name);
$ext2 = strtolower(end($ext1));
$target = "uploads/" . $file_name;
$alloweExtension = array("png", "jpg");
$fileErrors = null;
$ExistfileErrors = null;
$UsernameError = null;
$allowedError = null;
$username = user_input($_POST['UserInput']);
$password = user_input(md5($_POST['PassInput']));
if (empty($_FILES['FileInput']['name'])) {
$fileErrors = "Upload a file!!";
} else {
if (file_exists($target)) {
$ExistfileErrors = "File already exist";
} elseif (empty($_POST['UserInput'])) {
$UsernameError = "Username is Require";
} elseif (in_array($ext2, $alloweExtension)===false) {
$allowedError = "File not allowed.";
} else {
$insertQuery = mysqli_query($connection, "insert into users (username,password,filename)values('$username','$password','$file_name')");
if ($insertQuery) {
echo "you're success";
move_uploaded_file($file_tmp, $target);
} else {
die(mysqli_error($connection));
}
}
}
}
}
if (isset($allowedError))echo $allowedError;
if (isset($ExistfileErrors)) echo $ExistfileErrors;
?>
<form method="post" enctype="multipart/form-data">
Usrename : <input type="text" name="UserInput">
<?php if (isset($UsernameError))echo $UsernameError; ?> <br><br>
Password : <input type="password" name="PassInput"><br><br>
<input type="file" name="FileInput">
<?php if (isset($fileErrors)) echo $fileErrors; ?> <br><br>
<input type="submit" value="Register" name="Register">
</form>
</body>
</html>Hintئاماژە
Look at: the helper, every query, the password, the file name, the order of the checks and what is printed.
سەیری ئەمانە بکە: کردارە یاریدەدەرەکە، هەموو پرسێک، تێپەڕەوشەکە، ناوی فایلەکە، ڕیزبەندی پشکنینەکان و ئەوەی چاپ دەکرێت.
Show answerوەڵامەکە پیشان بدە
- SQL injection in both queries:
'$username','$password'and'$file_name'are pasted into the SQL.htmlspecialchars()in the helper is not SQL protection. - md5 password: fast and unsalted. Use
password_hash(). Runninguser_input()on the hash is pointless. user_input()changes the data: HTML entities and lower case are stored in the database;stripslashes()removes characters. Escape at output time instead.- The visitor's file name is used for
uploads/($target): it can contain../or replace another user's picture, and the duplicate check only says "File already exist". Make a new random name. - Checks in the wrong order: the empty-username check runs only after the database lookup and only when a file was sent.
- Success before the move: "you're success" is printed before
move_uploaded_file(), and its result is never checked. die(mysqli_error($connection))shows database details to visitors.- Smaller problems: "username already exist" is printed once per row inside a loop; typos ("Usrename", "Username is Require",
$alloweExtension); no password length check.
- لە هەردوو پرسەکەدا:
'$username'و'$password'و'$file_name'دەلکێنرێن بە SQLـەوە.htmlspecialchars()لە کردارە یاریدەدەرەکەدا پاراستنی SQL نییە. - تێپەڕەوشەی md5: خێرا و بێ خوێ.
password_hash()بەکاربهێنە. بەکارهێنانیuser_input()لەسەر هاشەکە بێسوودە. user_input()دراوەکە دەگۆڕێت: و پیتی بچووک لە بنکەدراوەدا هەڵدەگیرێن؛stripslashes()نووسە لادەبات. لە جیاتی ئەوە لە کاتی دەرخستەدا بێزیانی بکە.- ناوی فایلی سەردانیکەر بەکاردێت بۆ
uploads/($target): دەتوانێت../ـی تێدا بێت یان وێنەی بەکارهێنەرێکی تر بگۆڕێت و پشکنینی دووبارەبوونەوە تەنها دەڵێت «File already exist». ناوێکی هەڕەمەکیی نوێ دروست بکە. - پشکنینەکان بە ڕیزبەندی هەڵە: پشکنینی ناوی بەتاڵ تەنها دوای گەڕان لە بنکەدراوەدا و تەنها کاتێک فایلێک نێردرابێت کار دەکات.
- سەرکەوتن پێش گواستنەوە: «you're success» پێش
move_uploaded_file()چاپ دەکرێت و ئەنجامەکەی هەرگیز ناپشکنرێت. die(mysqli_error($connection))وردەکاری بنکەدراوە بە سەردانیکەران پیشان دەدات.- کێشەی بچووکتر: «username already exist» لەناو خولێکدا بۆ هەر ڕیزێک جارێک چاپ دەکرێت؛ هەڵەی نووسین («Usrename»، «Username is Require»،
$alloweExtension)؛ پشکنینی درێژیی تێپەڕەوشە نییە.
<?php
require "config.php"; // gives $conn and e()
$errors = [];
$success = "";
if (isset($_POST['Register'])) {
$username = trim($_POST['UserInput'] ?? ''); // keep the text as typed: no htmlspecialchars, no strtolower
$password = $_POST['PassInput'] ?? '';
$file = $_FILES['FileInput'] ?? null;
// 1. Validate everything first
if ($username === '' || mb_strlen($username) > 30) {
$errors[] = "Username is required (at most 30 characters).";
}
if (mb_strlen($password) < 8) {
$errors[] = "Password must have at least 8 characters.";
}
if ($file === null || $file['error'] !== UPLOAD_ERR_OK) {
$errors[] = "Upload a picture.";
} else {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ["png", "jpg"], true)) {
$errors[] = "Only .png and .jpg files are allowed.";
}
}
// 2. Is the username free? (a prepared statement)
if (!$errors) {
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ?");
$stmt->bind_param("s", $username);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
$errors[] = "This username already exists.";
}
}
// 3. Save the picture under a new random name, then insert the user
if (!$errors) {
$file_name = bin2hex(random_bytes(8)) . "." . $ext; // never reuse the visitor's file name
if (move_uploaded_file($file['tmp_name'], "uploads/" . $file_name)) {
$hash = password_hash($password, PASSWORD_DEFAULT); // not md5()
$stmt = $conn->prepare("INSERT INTO users (username, password, filename) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $hash, $file_name);
$stmt->execute();
$success = "Your account was created.";
} else {
$errors[] = "The picture could not be saved. Please try again.";
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<body>
<?php foreach ($errors as $error): ?>
<p style="color: red;"><?= e($error) ?></p>
<?php endforeach; ?>
<?php if ($success !== ""): ?>
<p style="color: green;"><?= e($success) ?></p>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
Username : <input type="text" name="UserInput" value="<?= e($_POST['UserInput'] ?? '') ?>"><br><br>
Password : <input type="password" name="PassInput"><br><br>
<input type="file" name="FileInput" accept=".png,.jpg"><br><br>
<input type="submit" value="Register" name="Register">
</form>
</body>
</html>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<p style="color: red;">Username is required (at most 30 characters).</p>
<p style="color: red;">Password must have at least 8 characters.</p>
<p style="color: red;">Upload a picture.</p>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 7
Keep the username as typed (only
trim).وەک نووسراوە بهێڵەوە (تەنها
trim).- Lineدێڕی 11-25
Validate everything first and collect all messages.
سەرەتا هەموو شتێک پشتڕاست بکەرەوە و هەموو پەیامەکان کۆبکەرەوە.
- Lineدێڕی 28-34
A prepared statement for the duplicate check.
بۆ پشکنینی دووبارەبوونەوە.
- Lineدێڕی 39
A new random file name: the visitor cannot choose the path.
ناوێکی هەڕەمەکیی نوێ بۆ فایلەکە: سەردانیکەر ناتوانێت ڕێڕەوەکە هەڵبژێرێت.
- Lineدێڕی 40-45
Save the file first, then insert, with
password_hash().سەرەتا فایلەکە پاشەکەوت بکە، پاشان زیادی بکە، بە
password_hash().
The fixed page was tested with a SQLite stand-in for $conn: an empty form, an existing username and a .php file all gave the right message, and the username <b>dilan</b> came back escaped in the form. Moving a real uploaded file can only be tested on a web server.
لاپەڕە چاککراوەکە بە جێگرەوەیەکی SQLite بۆ $conn تاقیکرایەوە: فۆرمێکی بەتاڵ، ناوێکی بەکارهێنەری هەبوو و فایلێکی .php هەموویان پەیامی دروستیان دا و ناوی <b>dilan</b> بە بێزیانی لە فۆرمەکەدا گەڕایەوە. گواستنەوەی فایلێکی بارکراوی ڕاستەقینە تەنها لەسەر تاقی دەکرێتەوە.
Exercisesڕاهێنانەکان
Exercises: ids and passwordsڕاهێنان: id و تێپەڕەوشەکان
A page receives ?id= and must show one product. Write the lines that read the id, reject anything that is not a whole number of 1 or more by redirecting to products.php, and load the product with a MySQLi prepared statement.
لاپەڕەیەک ?id= وەردەگرێت و دەبێت یەک بەرهەم پیشان بدات. ئەو دێڕانە بنووسە کە id دەخوێننەوە، هەر شتێک کە ژمارەیەکی تەواوی 1 یان زیاتر نییە بە ئاڕاستەکردنەوە بۆ products.php ڕەت بکەنەوە و بەرهەمەکە بە ڕستەیەکی ئامادەکراوی MySQLi بهێننەوە.
Show answerوەڵامەکە پیشان بدە
<?php
require "config.php";
$id = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if ($id === false || $id === null) {
header("Location: products.php");
exit;
}
$stmt = $conn->prepare("SELECT id, name, price FROM product WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$product = $stmt->get_result()->fetch_assoc(); // null if there is no such productAn old page stores passwords with md5() and logs in with WHERE username='$u' AND password='" . md5($p) . "'. Describe the two changes needed to make registration and login safe.
لاپەڕەیەکی کۆن تێپەڕەوشە بە md5() هەڵدەگرێت و بە WHERE username='$u' AND password='" . md5($p) . "' دەچێتە ژوورەوە. ئەو دوو گۆڕانکارییە بنووسە کە بۆ سەلامەتکردنی خۆتۆمارکردن و چوونەژوورەوە پێویستن.
Show answerوەڵامەکە پیشان بدە
- Registration: store
password_hash($p, PASSWORD_DEFAULT)in aVARCHAR(255)column, inserted with a prepared statement. - Login: select the row by username only with a prepared statement, then check
password_verify($p, $row['password'])in PHP. Show one message for every failure.
- خۆتۆمارکردن:
password_hash($p, PASSWORD_DEFAULT)لە ستوونێکیVARCHAR(255)ـدا هەڵبگرە، بە زیادی بکە. - چوونەژوورەوە: ڕیزەکە تەنها بە ناوی بەکارهێنەر بە هەڵبژێرە، پاشان لە PHPـدا
password_verify($p, $row['password'])بپشکنە. بۆ هەموو شکستێک یەک پەیام پیشان بدە.
The register() and login() functions in the password section are a complete, tested answer. Users with old md5 hashes must set a new password, because md5 cannot be turned back into the password.
کردارەکانی register() و login() لە بەشی تێپەڕەوشە وەڵامێکی تەواو و تاقیکراوەن. بەکارهێنەرانی خاوەن هاشی کۆنی md5 دەبێت تێپەڕەوشەیەکی نوێ دابنێن، چونکە md5 ناگەڕێتەوە بۆ تێپەڕەوشەکە.
Summary and reviewپوختە و پێداچوونەوە
| Dangerمەترسی | Defenceبەرگری | In PHPلە PHPـدا |
|---|---|---|
| SQL injection | keep data out of the SQL textدراوە لە دەقی SQL دوور بخەرەوە | prepare() + execute() / bind_param() |
| Nonsense or harmful valuesنرخی بێواتا یان زیانبەخش | validate on the serverلەسەر ڕاژەکار پشتڕاستی بکەرەوە | filter_var(), in_array(..., true), mb_strlen() |
| XSS | escape outputدەرخستە بێزیان بکە | htmlspecialchars($v, ENT_QUOTES, 'UTF-8') |
| Stolen passwordsدزینی تێپەڕەوشە | slow, salted hashesهاشی خاو و خوێدار | password_hash(), password_verify() |
| Too much powerدەسەڵاتی زۆر | least privilege | GRANT SELECT, INSERT, UPDATE, DELETE ON db.* |
| Information leaksدزەکردنی زانیاری | log details, show short messagesوردەکاری تۆمار بکە، پەیامی کورت پیشان بدە | try/catch + error_log() |
What is SQL injection?تێخزاندنی SQL چییە؟
An attack in which text from the visitor becomes part of an SQL command because the script glued it into the SQL string. It can show, change or delete data and bypass logins.
هێرشێکە کە تێیدا نووسینی سەردانیکەر دەبێتە بەشێک لە فەرمانێکی SQL، چونکە دەسنووسەکە لکاندوویەتی بە زنجیرەی SQLـەکەوە. دەتوانێت دراوە پیشان بدات، بیگۆڕێت یان بیسڕێتەوە و تێپەڕێنێت.
What does ' OR '1'='1 do in WHERE id='...'?' OR '1'='1 لە WHERE id='...'دا چی دەکات؟
The quote closes the text and OR '1'='1' makes the condition true for every row, so every row is selected, updated or deleted.
کۆتەیشنەکە دەقەکە دادەخات و OR '1'='1' مەرجەکە بۆ هەموو ڕیزێک ڕاست دەکات، بۆیە هەموو ڕیزێک هەڵدەبژێردرێت، نوێ دەکرێتەوە یان دەسڕدرێتەوە.
Why does admin' -- log in without a password?بۆچی admin' -- بەبێ تێپەڕەوشە دەچێتە ژوورەوە؟
-- starts an SQL comment, so the AND password = ... part is ignored and only the username is checked.
-- لێدوانێکی SQL دەست پێدەکات، بۆیە بەشی AND password = ... پشتگوێ دەخرێت و تەنها دەپشکنرێت.
Name the four steps of a MySQLi prepared statement.چوار هەنگاوەکەی ڕستەی ئامادەکراوی MySQLi بژمێرە.
prepare() with ?, bind_param() with type letters (s, i, d, b), execute(), then get_result() / affected_rows.
prepare() بە ?، bind_param() بە پیتەکانی جۆر (s، i، d، b)، execute()، پاشان get_result() / affected_rows.
Is md5() encryption?ئایا md5() شفرەکردنە؟
No. It is a hash function: one-way, fixed length (32 hex characters). Encryption can be reversed with a key. sha1() gives 40 hex characters.
نەخێر. کرداری هاشە: یەک ئاڕاستە، درێژیی جێگیر (32 پیتی شازدەیی). شفرەکردن بە کلیلێک دەگەڕێتەوە. sha1() چل پیتی شازدەیی دەدات.
Why not use md5 or sha1 for passwords?بۆچی md5 یان sha1 بۆ تێپەڕەوشە بەکارنەهێنین؟
They are very fast, have no salt (the same password always gives the same hash) and are broken. Use password_hash() and password_verify().
زۆر خێران، خوێیان نییە (هەمان تێپەڕەوشە هەمیشە هەمان هاش دەدات) و شکاون. password_hash() و password_verify() بەکاربهێنە.
What is the principle of least privilege?بنەمای کەمترین مۆڵەت چییە؟
Give every account only the rights it needs. A web application gets its own database user with, for example, only SELECT, INSERT, UPDATE and DELETE on its own database.
هەر هەژمارێک تەنها ئەو مافانە وەربگرێت کە پێویستیەتی. بەکارهێنەرێکی بنکەدراوەی تایبەتی خۆی وەردەگرێت کە، بۆ نموونە، تەنها SELECT و INSERT و UPDATE و DELETEـی لەسەر بنکەدراوەکەی خۆی هەیە.
What should a visitor see when a query fails?کاتێک پرسێک شکست دەهێنێت سەردانیکەر دەبێت چی ببینێت؟
A short general message. The SQL and the database error go to the server's error log, never to the page.
پەیامێکی گشتیی کورت. SQL و هەڵەی بنکەدراوە دەچن بۆ لۆگی هەڵەکانی ڕاژەکار، هەرگیز بۆ لاپەڕەکە.
Videoڤیدیۆ
Watch a videoسەیری ڤیدیۆیەک بکە
A video that viewers liked a lot and praised in their comments, chosen for this lesson.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.
How SQL injection changes a PHP database query, why escaping is only a weak fix, and why prepared statements are the right one.
چۆن لە دەگۆڕێت، بۆچی escaping تەنها چارەسەرێکی لاوازە و بۆچی چارەسەری ڕاستن.
Whole video: what SQL injection is and why prepared statements fix it
هەموو ڤیدیۆکە: چییە و بۆچی چارەسەری دەکەن.
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
Building a CRUD Application with PHP and MySQLدروستکردنی بەرنامەیەکی CRUD بە PHP و MySQL
Related lecturesوانە پەیوەندیدارەکان
- Building a CRUD Application with PHP and MySQLدروستکردنی بەرنامەیەکی CRUD بە PHP و MySQL
- A Login System with Sessionsسیستەمی چوونەژوورەوە بە سێشن
- Form Validationپشتڕاستکردنەوەی فۆرم
- SQLite Basicsبنەماکانی SQLite
- Injection Attacks: SQL Injection and Cross-Site Scriptingهێرشەکانی تێخزاندن: تێخزاندنی SQL و XSS
- Encryption, Hashing and Digital Signaturesشفرەکردن، هاشکردن و واژووە دیجیتاڵییەکان
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
- PHP Manual: SQL Injection
- PHP Manual: Prepared statements (MySQLi)
- PHP Manual: PDO::prepare
- PHP Manual: filter_var
- PHP Manual: htmlspecialchars
- PHP Manual: password_hash
- PHP Manual: Safe Password Hashing (FAQ)
- PHP Manual: md5 (note on password hashing)
- MySQL 8.0 Reference Manual: GRANT Statement
- OWASP: SQL Injection Prevention Cheat Sheet
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.