It can be done using a virtual layer. This query first collects all start and end points into one table, then selects those points which intersect only once with the lines layer.
Assuming the line layer is called edges
and its geometry field is called geometry
:
SELECT * from
(SELECT st_startpoint(geometry) as geom from edges
UNION
SELECT st_endpoint(geometry) AS geom FROM edges) AS startend
JOIN edges
ON st_intersects(startend.geom, edges.geometry)
GROUP BY geom
HAVING count(*) = 1
Note: The seemingly missing point towards the lower left of the image is actually a cul-de-sac and not a dangle!