2018-11-13

Timing attack là gì? - Dò pass FB.

Timing attack là gì?

Vài tháng trước tui từng viết về Billion Laugh Attack, một loại hình tấn công lợi dụng entity reference expansion trong XML để làm tràn bộ nhớ hệ thống. Trong bài viết này, tui sẽ giới thiệu Timing Attack, một kiểu tấn công cũng có thể ảnh hưởng đến miếng cơm manh áo của một kĩ sư phần mềm như bạn.

Photo by Bru-nO on Pixabay.
Timing attack là một loại side-channel attack (tấn công kênh kề) trong ngành mật mã học, kiểu tấn công dựa vào những thông tin thu thập được từ cách thực thi của hệ thống. Cụ thể ở đây timing attack phân tích thời gian thực thi của ứng dụng, để từ đó tiến hành các mưu đồ xấu độc. Xấu độc :thinking_face:, nghe y hệt cách chính phủ mô tả mạng xã hội ha :speak_no_evil:.
Bằng cách đo đạc thời gian thực thi của một số thao tác nhất định, hacker có thể sử dụng những thông tin này để truy dần ra dữ liệu đầu vào.
Ô kê phai! Nhưng thân là kĩ sư phần mềm, timing attack thì có ảnh hưởng gì đến chén cơm của tui?
Nhưng đâm ra nó có. Kiểu tấn công này thường dễ bị bỏ hớ, lại hay đến từ cách ta viết code. Những kẻ tấn công lợi dụng bản năng của chúng ta, những kĩ sư luôn muốn code của mình chạy càng nhanh càng tốt, để thực hiện ý đồ tấn công. Đúng nghĩa mất dạy là mẹ thành công mà :cry:.
Ta cùng lướt qua hai ví dụ sau để hiểu rõ thêm.

Password timing attack

API của một website không nổi tiếng lắm sử dụng chữ kí HMAC để verify payload gửi lên server. Đoạn code verify của họ dùng ngôn ngữ Ruby được viết như vầy:
def verify(payload, key, signature)
  hmac(payload) == signature
end

private
def hmac(data)
  # actual implementation
end
Đoạn code nhìn sơ qua không có gì vấn đề gì quá đặc biệt :thinking_face:. Nó chỉ tạo lại chữ ký HMAC cho payload và so trùng với chữ ký được người dùng đưa lên.
Để biết vấn đề là gì, ta hãy nghiên cứu cách Ruby hiện thực hàm so sánh chuỗi của nó.
str_eql(const VALUE str1, const VALUE str2)
{
    const long len = RSTRING_LEN(str1);
    const char *ptr1, *ptr2;

    if (len != RSTRING_LEN(str2)) return Qfalse;
    if (!rb_str_comparable(str1, str2)) return Qfalse;
    if ((ptr1 = RSTRING_PTR(str1)) == (ptr2 = RSTRING_PTR(str2)))
        return Qtrue;
    if (memcmp(ptr1, ptr2, len) == 0)
        return Qtrue;
    return Qfalse;
}
Cách hiện thực của Ruby khá đơn giản: đầu tiên nó kiểm tra độ dài của hai chuỗi là bằng nhau (1), sau đó thử so sánh hai con trỏ (2), rồi dùng memcmp(3) để kiểm tra hai string (3). Vấn đề nằm ở (3). memcmp(3), như khuyến cáo trên Linux man page, không nên được dùng để so sánh cryptographic data. Lý do memcmp(3) là short-circuit, thời gian chạy của nó phụ thuộc vào hai con trỏ đầu vào, chính xác hơn là số byte đầu giống nhau giữa chúng. Như đoạn code so sánh aaa và bbb sẽ trả về nhanh hơn đoạn so sánh aaa và aab.
Đây là cách hiện thực hợp lý và thông minh. Nếu các byte đầu đã là không khớp, việc gì phải kiểm tra thêm các byte sau. Tuy nhiên khi dùng để so sánh hai chuỗi mật mã, cách hiện thực này có thể vô tình làm tiết lộ thông tin của chuỗi thông qua thời gian thực thi của nó.
Giả sử bạn có hai chuỗi bytes 1A 2B 3C 4D 5E 6F và 1A 00 00 00 00 00, nếu memcmp(3) tốn 1ms để trả về, thì 1A 2B 3C 4D 5E 6F và 1A 2B 3C 4D 5E 6F sẽ tốn 6ms. Đương nhiên 11 11 11 11 và 00 00 00 00 sẽ trả về ngay lập tức.
Hacker có thể lợi dụng những thông số này để tiến hành mưu đồ của chúng. Đầu tiên chúng tạo ra và thử lần lượt những HMAC signature sau:
0000000000000000000000000000000000000000
0100000000000000000000000000000000000000
0200000000000000000000000000000000000000
... lược bỏ 250 cái ....
FD00000000000000000000000000000000000000
FE00000000000000000000000000000000000000
FF00000000000000000000000000000000000000
Sau đó chúng tìm được A100000000000000000000000000000000000000, signature này tốn lâu hơn một vài mi-li-giây để trả về so với những chuỗi khác. Suy ra chuỗi chính xác bắt đầu bằng A1. Áp dụng thủ thuật tương tự cho 19 đoạn 00 còn lại, chúng có được signature mong muốn :scream:.
Để cho xã hội vững mạnh tiến lên 4.0, chúng ta phải làm cho hàm này chạy chậm 4.0 lần cần loại bỏ yếu tố short-circuit và làm cho thuật toán verify có constant-time. Tức là bắt nó chạy với thời gian cố định mà không phụ thuộc vào input.
def verify(message, signature)
  secure_compare(hmac(payload), signature)
end

# https://github.com/plataformatec/devise/blob/94adec3ceef6a3e9eb6bdc4d3fa7ed4a3fc6b348/lib/devise.rb#L500-L508
def self.secure_compare(a, b)
  return false if a.blank? || b.blank? || a.bytesize != b.bytesize
  l = a.unpack "C#{a.bytesize}"

  res = 0
  b.each_byte { |byte| res |= byte ^ l.shift }
  res == 0
end

Username timing attack

Chắc bạn từng viết login form. Thông thường, khi user nhập sai thông tin login, chúng ta hay trả về những thông báo chung chung như "email or password is invalid", thay vì thông báo chính xác cái nào bị sai. Làm vậy để tránh để lộ dữ liệu nhạy cảm như email của người dùng :bulb: :hugging_face:.
Tuy nhiên, với timing attack, hacker vẫn có thể đạt được ý đồ xấu xa đó. Ví dụ như đoạn code login sau đây:
def login(email, password)
  user = User.find_by(email: email)
  return false if !user
  return encrypt_password(password) == user.encrypted_password
end
Bạn có để ý là: đoạn code trên cũng có short-circuit :face_with_head_bandage:. Cùng là password sai, nhưng login với một email tồn tại sẽ tốn thời gian nhiều hơn đáng kể (để mã hóa password) so với một email không tồn tại.
Những kẻ chống phá có thể lợi dụng lỗ hổng này để dò email. Nếu login bằng a@quan-cam.com tốn 135.34ms nhiều hơn so với b@quan-cam.com và c@quan-cam.com, chúng sẽ đoán ra a@quan-cam.com là một email hợp lệ, còn thời gian dôi ra là hệ thống hash password. Cứ thử liên tục như thế, hắn sẽ có được tập email mong muốn từ hệ thống của chúng ta.
Người ta có nghĩ ra một vài cách để chống kiểu tấn công này. Ý tưởng không khác, đó là làm cho thuật toán chạy constant time. Bạn luôn luôn hash password dù cho username có hợp lệ hay không, hoặc cho CPU sleep một khoảng thời gian bằng với thuật toán hash. Nhờ đó, code của bạn sẽ chạy chậm một cách ổn định.
def login(email, password)
  user = User.find_by(email: email)

  if user
    return encrypt_password(password) == user.encrypted_password
  else
    encrypt_password("just-to-waste-some-time")
    return false
  end
end
Thật là cực chẳng đã đúng không bà con? :sweat:

Cơ mà ... ông có dụ tui không?

Đọc tới đây có thể bạn sẽ nghĩ tui đang dụ bạn, vì cách tấn công này nghe có vẻ hơi bất khả thi. Có quá nhiều nhân tố có thể ảnh hưởng đến thời gian thực thi và trả về của một web: như network, sức mạnh của máy, cá mập, cách mạng, 4.0, 2.02.0 v.v. Với lại, sao mà hacker biết được code bạn viết thế nào?
Theo nghiên cứu Opportunities and Limits of Remote Timing Attacks, họ có thể nhận diện được độ khác biệt của remote timing trong mức dao động 20µs, và mạng LAN ở mức 100ns (thậm chí có thể nhỏ hơn) chỉ trên một tập vài trăm hoặc vài ngàn request.
Với kết quả từ nghiên cứu này, đoạn tấn công HMAC ở trên hoàn toàn có thể thực hiện được với khoảng 5,000,000 request (20 x 256 x 1000). Đó là con số có thể thực hiện chỉ trong vòng 3 ngày với tỉ lệ là 20 request/s.
Còn code bạn viết thế nào thì ... tui e rằng nó có đầy trên mạng ấy. Bạn có dùng thư viện mã nguồn mở mà đúng không :wink:?

Bài viết này sẽ giúp tui tăng lương thế nào?

Bài viết này không những không giúp bạn tăng lương, mà còn khuyên bạn làm cho code của bạn chậm đi :cry:.
Túm cái váy là, tui mong bài viết này đã giới thiệu cho bạn thấy:

  • khái niệm về timing attack: tấn công bằng cách phân tích thời gian thực thi của hệ thống.
  • cách phòng chống là thiết kế thuật toán của bạn chạy constant time mà không phụ thuộc vào input.
  • code C nhìn xấu như thế nào :pensive:.

21 trang web hay mà bạn có thể ghé thăm mỗi khi rảnh rỗi

21 trang web hay mà bạn có thể ghé thăm mỗi khi rảnh rỗi - Tác Giả: Juno okyo

1. TED

Với hơn 1900 bài talk thú vị và đầy cảm hứng, chắc chắn bạn sẽ tìm được điều gì đó mà bạn thích.

2. Difference Between

Bạn muốn tìm hiểu sự khác biệt giữa bất cứ điều gì? Ví dụ giữa API và Web Service.

3. The Geocities-izer

Biến bất cứ trang web nào thành một trang với giao diện xấu xí như những năm 90.

4. Wikipedia Random

Rơi vào hố thỏ và khám phá những chủ đề ngẫu nhiên trên Wikipedia.

5. Whizzpast

Nơi tuyệt vời nhất trên Internet để tìm hiểu về những điều tuyệt vời trong lịch sử.

6. Find the Invisible Cow

Hãy chắc chắn rằng bạn đã mở loa (nhưng đừng quá to :trollface:).

7. Sporcle

Giải hàng ngàn câu đố hoặc tự bạn tạo ra.

8. Zooniverse

Trở thành một phần của những dự án khoa học thực sự (ví dụ như khám phá bề mặt của mặt trăng) bằng cách tham gia nghiên cứu online.

9. Lang-8

Viết trong một ngôn ngữ mà bạn đang học và người bản xứ sẽ sửa các lỗi của bạn.

10. Silk

Tạo ra các tác phẩm nghệ thuật với con chuột của bạn.

11. Letters of Note

Một bộ sưu tập tuyệt vời của các chữ cái hấp dẫn, bưu thiếp, điện tín và các ghi chú trong quá khứ.

12. Khan Academy

Nếu bạn đang muốn học hỏi thêm điều gì đó thì Khan Academy có hàng trăm khóa học giáo dục miễn phí dành cho bạn.

13. GeoGuessr

Đoán xem hình chụp bởi Google Streetview là ở nơi nào trên thế giới.

14. Feel Good Wardrobe

Trang cung cấp mẹo để mua quần áo.

15. I Need a Prompt

Giống trang You Should Write, nhưng dành cho các nghệ sĩ.

16. Omegle

Cảm thấy muốn nói chuyện với ai đó? Omegle giúp bạn nói chuyện (qua tin nhắn hoặc video) với một người lạ.

17. My Script Font

Tạo ra một font dựa trên chữ viết tay của chính bạn.

18. Live Plasma

Live Plasma là một cỗ máy khám phá âm nhạc. Chỉ cần gõ vào một nghệ sĩ mà bạn thích và nó sẽ hiển thị những nghệ sĩ tương tự.

19. Hippo Paint

Một cuốn sách tô màu hoàn hảo cho cả trẻ em và người lớn - chỉ cần gõ vào thứ gì đó mà bạn thích vẽ và nó sẽ cung cấp cho bạn những hình ảnh.

20. Akinator

Một phù thủy thực sự.

21. Kongregate


Hàng ngàn trò chơi trực tuyến miễn phí, từ những tên tuổi lớn cho đến tư nhân.

12 thủ thuật vô cùng hữu ích mà lập trình viên JavaScript cần biết - Tác Giả: Juno okyo

1. Chuyển đổi kiểu dữ liệu sang boolean sử dụng toán tử !!

Đôi lúc chúng ta cần kiểm tra nếu một vài biến tồn tại hoặc nếu nó có một giá trị hợp lệ hay không. Để xác nhận như vậy, bạn có thể sử dụng !! (toán tử phủ định đôi). Nó sẽ tự động chuyển đổi mọi kiểu dữ liệu về boolean và biến này sẽ trả về false chỉ khi nó có những giá trị như: 0, null, "", undefined hoặc NaN, ngược lại nó sẽ trả về true. Để hiểu hơn về cách nó hoạt động, hãy xem ví dụ đơn giản sau:
function Account(cash) {  
    this.cash = cash;
    this.hasMoney = !!cash;
}
var account = new Account(100.50);  
console.log(account.cash); // 100.50  
console.log(account.hasMoney); // true
var emptyAccount = new Account(0);  
console.log(emptyAccount.cash); // 0  
console.log(emptyAccount.hasMoney); // false
Trong ví dụ trên, nếu account.cash có giá trị lớn hơn 0 thì account.hasMoney sẽ có giá trị là true.

2. Chuyển đổi kiểu dữ liệu sang number sử dụng toán tử +

Thủ thuật này rất tuyệt vời và dễ dàng để thực hiện, nhưng nó chỉ hoạt động với chuỗi những con số, ngược lại nó sẽ trả về NaN (Not a Number). Hãy nhìn ví dụ sau:
function toNumber(strNumber) {  
    return +strNumber;
}
console.log(toNumber("1234")); // 1234  
console.log(toNumber("ACB")); // NaN
Thủ thuật này cũng hoạt động với cả Date và trong trường hợp này nó sẽ trả về timestamp:
console.log(+new Date()) // 1461288164385

3. Câu điều kiện rút gọn

Nếu bạn thấy một đoạn code giống như này:
if (connected) {  
    login();
}
Bạn có thể rút gọn nó bằng cách kết hợp một biến (sẽ được xác nhận) và một hàm sử dụng && (toán tử AND) ở giữa. Ví dụ, đoạn code trên có thể trở nên ngắn gọn trong một dòng:
connected && login();
Bạn có thể làm tương tự để kiểm tra nếu một vài thuộc tính hay hàm tồn tại trong object. Tương tự như đoạn code dưới đây:
user && user.login();

4. Đặt giá trị mặc định sử dụng toán tử ||

Ngày nay trong ES6 đã hỗ trợ tham số mặc định. Trong trường hợp bạn muốn giả lập tính năng này trong các trình duyệt cũ thì bạn có thể sử dụng || (toán tự OR) bằng cách chèn giá trị mặc định như là tham số thứ hai để sử dụng. Nếu tham số đầu tiên trả về false thì tham số thứ hai sẽ được sử dụng như là một giá trị mặc định. Xem ví dụ sau:
function User(name, age) {  
    this.name = name || "Oliver Queen";
    this.age = age || 27;
}
var user1 = new User();  
console.log(user1.name); // Oliver Queen  
console.log(user1.age); // 27
var user2 = new User("Barry Allen", 25);  
console.log(user2.name); // Barry Allen  
console.log(user2.age); // 25

5. Cache array.length trong vòng lặp

Mẹo này rất đơn giản và gây tác động lớn tới hiệu suất khi xử lý các mảng lớn trong vòng lặp. Hầu hết thì mọi người sử dụng vòng lặp for như sau để duyệt qua mảng:
for (var i = 0; i < array.length; i++) {  
    console.log(array[i]);
}
Nếu bạn làm việc với các mảng nhỏ thì không sao, nhưng nếu bạn xử lý những mảng lớn thì đoạn code này sẽ tính toán lại kích cỡ của mảng sau mỗi lần lặp và sẽ gây ra một chút chậm chễ (delay). Để tránh điều này, bạn có thể cache array.length trong một biến để dùng nó thay vì gọi array.length trong mỗi lần lặp:
var length = array.length;  
for (var i = 0; i < length; i++) {  
    console.log(array[i]);
}
Để làm nó trông gọn hơn, chỉ việc viết lại như sau:
for (var i = 0, length = array.length; i < length; i++) {  
    console.log(array[i]);
}

6. Nhận diện các thuộc tính trong một object

Thủ thuật này vô cùng hữu ích khi bạn cần kiểm tra nếu một vài thuộc tính tồn tại hay không và nó giúp tránh chạy các hàm hay thuộc tính chưa định nghĩa (undefined). Nếu bạn định viết code chạy trên đa trình duyệt thì bạn cũng có thể sử dụng kỹ thuật này. Ví dụ, hãy tưởng tượng rằng bạn cần viết code mà tương thích với IE6 và bạn muốn sử dụng document.querySelector() để lấy một vài phần tử bằng ID của chúng. Tuy nhiên, trong trình duyệt này thì hàm đó không tồn tại, vậy nên để kiểm tra xem hàm này có tồn tại không bạn có thể sử dụng toán tử in, xem ví dụ:
if ('querySelector' in document) {  
    document.querySelector("#id");
} else {
    document.getElementById("id");
}
Trong trường hợp này, nếu không có hàm querySelector trong document, chúng ta có thể sử dụng document.getElementById() thay thế.

7. Lấy phần tử cuối cùng trong mảng

Array.prototype.slice(begin, end) có thể cắt mảng khi bạn đặt tham số begin và end. Nhưng nếu bạn không nhập tham số end, hàm này sẽ tự động đặt giá trị lớn nhất cho mảng. Tôi nghĩ rằng ít người biết được hàm này có thể chấp nhận giá trị âm, và nếu bạn đặt tham số begin là một số âm thì bạn sẽ lấy ra các phần tử cuối cùng từ mảng:
var array = [1, 2, 3, 4, 5, 6];  
console.log(array.slice(-1)); // [6]  
console.log(array.slice(-2)); // [5,6]  
console.log(array.slice(-3)); // [4,5,6]

8. Cắt ngắn mảng

Kỹ thuật này có thể khóa kích cỡ mảng, điều này rất hữu ích để xóa một vài phần tử của mảng dựa trên số phần tử mà bạn muốn. Ví dụ, nếu bạn có một mảng với 10 phần tử nhưng bạn chỉ muốn lấy 5 phần tử đầu tiên thì bạn có thể cắt ngắn mảng, làm nó nhỏ hơn bằng cách đặt array.length = 5. Xem ví dụ sau:
var array = [1, 2, 3, 4, 5, 6];  
console.log(array.length); // 6  
array.length = 3;  
console.log(array.length); // 3  
console.log(array); // [1,2,3]

9. Thay thế toàn bộ

Hàm String.replace() cho phép sử dụng String và Regex để thay thế chuỗi, nhưng hàm này chỉ thay thế các chuỗi con xuất hiện đầu tiên. Nhưng bạn có thể giả lập một hàm replaceAll() bằng cách sử dụng /g ở cuối Regex:
var string = "john john";  
console.log(string.replace(/hn/, "ana")); // "joana john"  
console.log(string.replace(/hn/g, "ana")); // "joana joana"

10. Gộp các mảng

Nếu bạn cần gộp hai mảng thì có thể sử dụng hàm Array.concat():
var array1 = [1, 2, 3];  
var array2 = [4, 5, 6];  
console.log(array1.concat(array2)); // [1,2,3,4,5,6];
Tuy nhiên, hàm này không phải là cách thích hợp nhất để gộp các mảng lớn vì nó sẽ tốn rất nhiều bộ nhớ do tạo ra một mảng mới. Trong trường hợp này, bạn có thể sử dụng Array.push.apply(arr1, arr2). Thay vì tạo ra một mảng mới thì nó sẽ gộp mảng thứ hai vào mảng thứ nhất từ đó giảm việc sử dụng bộ nhớ:
var array1 = [1, 2, 3];  
var array2 = [4, 5, 6];  
console.log(array1.push.apply(array1, array2)); // [1,2,3,4,5,6];

11. Chuyển NodeList thành mảng

Nếu bạn chạy hàm document.querySelectorAll("p"), nó sẽ trả về một mảng chứa các phần tử DOM - NodeList object. Nhưng object này không có toàn bộ các hàm của mảng như: sort(), reduce(), map(), filter(). Trong trường hợp bạn muốn sử dụng các hàm này và nhiều hàm sẵn có khác của mảng, bạn cần chuyển NodeList thành mảng. Để thực hiện kĩ thuật này bạn chỉ cần dùng hàm: [].slice.call(elements)
var elements = document.querySelectorAll("p"); // NodeList  
var arrayElements = [].slice.call(elements); // Now the NodeList is an array  
var arrayElements = Array.from(elements); // This is another way of converting NodeList to Array

12. Xáo trộn các phần tử trong mảng

Để xáo trộn các phần tử của mảng mà không cần sử dụng các thư viện riêng như Lodash, bạn chỉ cần sử dụng thủ thuật sau:

var list = [1, 2, 3];  
console.log(list.sort(function() {  
    return Math.random() - 0.5
})); // [2,1,3]

Hướng dẫn về Regex

Hướng dẫn về Regex - Một bản tóm tắt nhanh bằng các ví dụ

Cụm từ thông dụng (regex hoặc regexp) cực kỳ hữu ích trong việc trích xuất thông tin từ bất kỳ văn bản nào bằng cách tìm kiếm một hoặc nhiều kết quả phù hợp của mẫu tìm kiếm cụ thể (ví dụ: chuỗi ký tự ASCII hoặc unicode cụ thể).
Các trường của phạm vi ứng dụng từ xác nhận hợp lệ đến phân tích cú pháp / thay thế chuỗi, đi qua việc dịch dữ liệu sang các định dạng khác và trích xuất web.
Một trong những tính năng thú vị nhất là khi bạn đã học cú pháp, bạn có thể sử dụng công cụ này trong hầu hết các ngôn ngữ lập trình (JavaScript, Java, VB, C #, C / C ++, Python, Perl, Ruby , Delphi, R, Tcl, và nhiều người khác) với sự phân biệt nhỏ nhất về sự hỗ trợ của các tính năng nâng cao và các phiên bản cú pháp được hỗ trợ bởi các công cụ).
Hãy bắt đầu bằng cách xem xét một số ví dụ và giải thích.

Chủ đề cơ bản

Neo - ^ và $

^ Các         kết quả phù hợp với bất kỳ chuỗi nào bắt đầu bằng  The -> Hãy thử nó! 
end $         khớp với chuỗi kết thúc bằng  kết thúc
^ Kết thúc chuỗi kết hợp chính xác $ (bắt đầu và kết thúc bằng Kết thúc )
roar         khớp với bất kỳ chuỗi nào có tiếng gầm trong đó

Định lượng - * +? và {}

abc *         khớp với một chuỗi có ab theo sau là 0 hoặc nhiều hơn c -> Hãy thử nó! 
abc +         khớp với chuỗi có ab theo sau bởi một hoặc nhiều c
abc ?         khớp với một chuỗi có ab theo sau là 0 hoặc 1 c
abc {2}       khớp với chuỗi có ab theo sau là 2 c
abc {2,}      khớp với một chuỗi có ab theo sau bởi 2 hoặc nhiều hơn c
abc {2,5}     khớp với chuỗi có ab theo sau là 2 đến 5 c
a (bc) *       khớp với chuỗi có tiếp theo là 0 hoặc nhiều bản sao của chuỗi bc
a (bc) {2,5}   khớp với chuỗi có tiếp theo là 2 đến 5 bản của chuỗi bc

Toán tử OR - | hoặc là []

a (b | c)      khớp với một chuỗi có dấu sau là b hoặc c -> Thử nó!
một [bc]       giống như trước

Các lớp ký tự - \ d \ w \ s và.

\ d          phù hợp với một ký tự đơn là một chữ số -> Hãy thử nó! 
\ w          khớp với một ký tự từ (ký tự chữ và số cộng với gạch dưới) -> Dùng thử!
\ s          khớp với ký tự khoảng trắng (bao gồm các tab và ngắt dòng)
.          phù hợp với bất kỳ nhân vật -> Hãy thử nó! 
Sử dụng  .toán tử cẩn thận vì lớp thường hoặc lớp nhân vật phủ định (chúng tôi sẽ đề cập đến tiếp theo) nhanh hơn và chính xác hơn.
\d, \wVà \scũng trình bày phủ nhận của họ với \D, \Wvà \Stương ứng.
Ví dụ, \Dsẽ thực hiện kết quả nghịch đảo đối với kết quả thu được \d.
\ D          khớp với một ký tự không phải là chữ số -> Hãy thử! 
Để được thực hiện theo nghĩa đen, bạn phải thoát khỏi các ký tự ^.[$()|*+?{\bằng dấu gạch chéo ngược \vì chúng có ý nghĩa đặc biệt.
\ $ \ d        phù hợp với chuỗi có $ trước một chữ số -> Dùng thử! 
Lưu ý rằng bạn có thể khớp với các ký tự không in được như tab \t, dòng mới \n, trả về vận chuyển \r.

Cờ

Chúng tôi đang học cách xây dựng một regex nhưng quên một khái niệm cơ bản: cờ .
Một regex thường đi kèm với dạng này , trong đó mẫu tìm kiếm được phân cách bởi hai ký tự gạch chéo . Cuối cùng, chúng ta có thể chỉ định một cờ với các giá trị này (chúng ta cũng có thể kết hợp chúng với nhau):/abc//
  • g (toàn cầu) không trở lại sau trận đấu đầu tiên, khởi động lại các tìm kiếm tiếp theo từ cuối trận đấu trước đó
  • m (nhiều dòng) khi được bật ^và $sẽ khớp với phần bắt đầu và kết thúc của một dòng, thay vì toàn bộ chuỗi
  • i (không nhạy cảm) làm cho toàn bộ biểu thức không phân biệt chữ hoa chữ thường (ví dụ /aBc/isẽ khớp AbC)

Chủ đề trung gian

Nhóm và chụp - ()

a ( bc )            dấu ngoặc đơn tạo một nhóm chụp với giá trị bc -> Hãy thử nó! 
một (: bc ) * sử dụng : chúng tôi vô hiệu hóa các nhóm chụp -> Hãy thử nó! 
a (? <foo> bc )      bằng cách sử dụng ? <foo> chúng tôi đặt tên cho nhóm -> Dùng thử!
Toán tử này rất hữu ích khi chúng ta cần trích xuất thông tin từ chuỗi hoặc dữ liệu bằng ngôn ngữ lập trình ưa thích của bạn. Bất kỳ nhiều lần xuất hiện được một số nhóm chụp sẽ được hiển thị dưới dạng một mảng cổ điển: chúng ta sẽ truy cập vào các giá trị của chúng chỉ định sử dụng một chỉ mục trên kết quả của trận đấu.
Nếu chúng ta chọn đặt tên cho các nhóm (sử dụng ), chúng ta sẽ có thể lấy các giá trị nhóm bằng cách sử dụng kết quả phù hợp như một từ điển mà các phím sẽ là tên của mỗi nhóm.(?<foo>...)

Biểu thức khung - []

[abc]             phù hợp với một chuỗi mà có hoặc là một hoặc ab hoặc ac ->  là giống như a | b | c -> Hãy thử nó! 
[ac]             giống như trước
[a-fA-F0-9]       một chuỗi đại diện cho một chữ số thập lục phân đơn lẻ, không phân biệt chữ hoa chữ thường -> Hãy dùng thử! 
[0-9]%            một chuỗi có ký tự từ 0 đến 9 trước dấu%
[^ a-zA-Z]         một chuỗi không có chữ cái từ a đến z hoặc từ A đến Z. Trong trường hợp này dấu ^ được sử dụng làm từ chối biểu thức -> Hãy thử nó! 
Hãy nhớ rằng bên trong khung biểu thức tất cả các ký tự đặc biệt (bao gồm cả dấu gạch chéo ngược \) mất quyền hạn đặc biệt của chúng: vì vậy chúng tôi sẽ không áp dụng "quy tắc thoát".

Tham lam và lười biếng

Các định lượng ( * + {}) là toán tử tham lam, do đó chúng mở rộng kết quả phù hợp với chúng thông qua văn bản được cung cấp.
Ví dụ: <.+>đối sánh <div>simple div</div>trong . Để chỉ bắt được thẻ, chúng tôi có thể sử dụng thẻ  để làm cho nó lười biếng:This is a <div> simple div</div> testdiv?
<. +?>             khớp với bất kỳ ký tự nào một hoặc nhiều lần được bao gồm bên trong < và > , mở rộng khi cần -> Hãy thử! 
Lưu ý rằng một giải pháp tốt hơn nên tránh việc sử dụng có  .lợi cho một regex nghiêm ngặt hơn:
<[^ <>] +>          khớp với bất kỳ ký tự nào ngoại trừ <hoặc> một hoặc nhiều lần được bao gồm bên trong < và > -> Hãy dùng thử! 

Chủ đê nâng cao

Ranh giới - \ b và \ B

\ b abc \ b           thực hiện tìm kiếm "chỉ toàn bộ từ" -> Dùng thử! 
\bđại diện cho một neo giống như dấu mũ (tương tự $và ^) khớp với một bên là một ký tự từ (như \w) và mặt kia không phải là ký tự từ (ví dụ nó có thể là đầu chuỗi hoặc ký tự khoảng trắng).
Nó đi kèm với nó phủ định , \B. Điều này khớp với tất cả các vị trí \bkhông khớp và có thể là nếu chúng ta muốn tìm mẫu tìm kiếm được bao quanh hoàn toàn bởi các ký tự từ.
\ B abc \ B           chỉ phù hợp nếu mẫu được bao quanh hoàn toàn bởi các ký tự từ -> Hãy thử!

Tham chiếu ngược - \ 1

( [abc] ) \ 1               bằng cách sử dụng \ 1 nó khớp với cùng một văn bản được so khớp bởi nhóm chụp đầu tiên -> Hãy thử nó! 
([abc]) ( [de] ) \ 2 \ 1 chúng ta có thể sử dụng \ 2 (\ 3, \ 4, v.v.) để xác định cùng một văn bản được so khớp bởi nhóm chụp thứ hai (thứ ba, thứ tư, v.v.) -> Thử đi! 
(? <foo> [abc] ) \ k <foo>    chúng tôi đặt tên t foo o nhóm và chúng tôi tham khảo nó sau này ( \ k <foo> ). Kết quả là giống nhau của regex đầu tiên -> Hãy thử nó!

Nhìn về phía trước và nhìn phía sau -  (? =) Và  (? <=)

d (? = r )        phù hợp với chỉ khi d được theo sau bởi r , nhưng r sẽ không là một phần của kết hợp regex tổng thể -> Hãy thử nó! 
(? <= r ) d phù hợp với chỉ một d nếu được bắt đầu bởi một r , nhưng r sẽ không là một phần của kết hợp regex tổng thể -> Hãy thử nó! 
Bạn cũng có thể sử dụng toán tử phủ định!
d (?! r ) chỉ        khớp với d nếu không được theo sau bởi r , nhưng r sẽ không là một phần của kết hợp regex tổng thể -> Hãy thử nó! 
(? <! r ) d chỉ phù hợp với một d nếu không được bắt đầu bằng r , nhưng r sẽ không là một phần của trận đấu regex tổng thể -> Hãy thử nó!